Wednesday, March 14, 2012

How to make a Twilio Voice App Hosted by Heroku

Twilio is a relatively new cloud-based platform that allows one to create and scale voice, VoIP and SMS text messaging applications.  Here, we'll walk through building an app to allow a caller to check the high and low tide times in Santa Monica for the day of the call.  We'll use TwiML, Twilio's XML-like language for issuing HTTP GET and POST requests between your server and theirs. 

In order to run your app, you'll need to find a place to host your code.  For this example, I'll use a basic Heroku app.  First, we'll build the Heroku app using ruby and Sinatra, then we'll incorporate the TwiML and the Twilio Sandbox.

If you don't already have a github account and its version control system installed on your local machine, follow these directions.  If you don't already have a Heroku account or aren't setup to push code from your local machine, follow these instructions.  If you have no idea what I'm talking about, perhaps this panda can set you straight.

Rather than reinvent the wheel, we'll clone an existing github repository that has a basic skeleton for making one page static sites on Heroku.

# Make yourself a directory for the app
$ mkdir twilio_static

# Pull a clone of the skeleton app
$ git clone git://github.com/dpritchett/wwebsite.git twilio_static/
$ cd twilio_static/

The ./twilio_static/Gemfile looks like this
source 'http://rubygems.org'
gem 'sinatra'
gem 'heroku'

# For Twilio App
gem 'hpricot'
gem 'twilio-ruby'
gem 'builder'

Install the gems...
$ bundle install

Now we need to create a repository on github.

$ git init
$ git add .
$ git commit -m "initial commit"

Go to github and create new repository on github: https://github.com/<username>
Next, associate the local files with the new repository.

# Got an error because the local name of the remote repository already exists
$  git remote add origin git@github.com:jonesbb/twilio_static.git
fatal: remote origin already exists.

# Found a workaround on Stack Overflow and was able to continue

$  git remote rm origin
$  git remote add origin git@github.com:jonesbb/twilio_static.git
$  git push -u origin master

# now create heroku app

$ heroku create
$ git push heroku master

To get a preview of the basic app, you can run the server locally
$ ruby -rubygems app.rb

Point your browser here to see the app on your local server: http://localhost:4567/

*** You need to restart the app.rb file each time you change it! ***

Get a xml file of tide data from NOAA and place it in the /public folder of the app.  For extra credit you could have the app extract that data directly from the NOAA servers, but that's outside the scope of this demo.

Start a ruby script to parse through the tide data and output the high/low tides for the current day.

Here's what's in the initial version of my public/twilio_tides.rb
require 'rubygems'
require 'hpricot'
require 'builder'


# Get Today's Date in yyyy/mm/dd format
t=Time.now
cdate = t.strftime("%Y/%m/%d")


# Read in the tides xml file
xml = File.read('public/9410840_2012_tides.xml')


# Initiate the variable, prepending an intro.
@tide_today = ["Today's Tides are as follows:"]


# Find the tide info for today's date
# There's usually more than one hi/lo for a day, so push all into an array
doc = Hpricot::XML(xml)
(doc/:item).each do |dat|
    tide_date = (dat/'date').inner_html
    tide_time = (dat/'time').inner_html
    tide_size = (dat/'predictions_in_ft').inner_html
    tide_hilo = (dat/'highlow').inner_html
    if tide_date == cdate then
        if tide_hilo == "H" then
            tide_hilo = "high"
        elsif tide_hilo == "L" then
            tide_hilo = "low"
        end
      @tide_today << "#{tide_hilo} tide, #{tide_size}feet at #{tide_time}"
    end
end

# Join the array into one string and define instance variable to pass to app.rb
@@tide_today = @tide_today.map! { |p| "#{p}" }.join(", ")

Add to the app.rb to test out the new twilio_tides.rb script.
# Setup a test of the tides script
get '/hello' do
  puts "Hello, world!"
  load 'public/twilio_tides.rb'
  puts "Here's tide info: #{@@tide_today}"
end
Run the app locally as discussed above and navigate to:
http://localhost:4567/hello (which initiates the get request to run the code in /hello).
You should see the "Hello World" and "Here's the tide info:..." print with the tide data for today.

Now you're ready for the TwiML creation.  The additional code in app.rb below makes use of the TwiML verbs, <Say> and <Gather> to tell the caller what the tides are and then to gather the caller's feedback regarding the option of repeating the info or ending the call.

Here's the final app.rb file.
require 'sinatra'
require 'builder'

# Define the route for the index.html - static homepage for the app
get '/' do
      File.read(File.join('public', 'index.html'))
end

# Setup a test of the tides script
get '/hello' do
  puts "Hello, world!"
  load 'public/twilio_tides.rb'
  puts "Here's tide info: #{@@tide_today}"

end

# Define a method that accepts both post and get requests
# This is useful because of the /loop redirect to /tides
def get_or_post(path, opts={}, &block)
  get(path, opts, &block)
  post(path, opts, &block)
end


# Create the TwiML response to read out the tide data
get_or_post '/tides' do
  load 'public/twilio_tides.rb'
  builder do |xml|
    xml.instruct!
    xml.Response do
            xml.Say("Hi #{@@tide_today}")
            xml.Gather(:action=>"/loop", :numDigits => 1) do
                  xml.Say("Press one to repeat or two to end this call.")
            end
    end
  end
end

# Loop the caller's feedback
post '/loop' do
    if !params['Digits'] or params['Digits'] != '1'
        builder do |xml|
              xml.instruct!
              xml.Response do
                xml.Say("Adios.")
              end
        end
    else
      redirect '/tides'
    end
end

Now let's push all the code to Heroku so we can access it from Twilio.

$ git add .
$ git commit -m "added TwiML to app"
$ git push
$ git push heroku

Take note of the Heroku
http://sharp-stream-4312.heroku.com

Go to Twilio and create an account (if you haven't got one already).  On your account's Dashboard, you'll see the "Sandbox."  Put the new Heroku URL, directing to the get_or_post method's /tides, in the Sandbox's Voice URL.

http://sharp-stream-4312.heroku.com/tides

Click SAVE and then try calling.  For more information, Twilio's website has many more examples of how to interface with their servers using either their REST API or TwiML.