Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Merge branch 'master' of github.com:twilio/workshop

  • Loading branch information...
commit c9392798114e3d174f2c20b7a9d141573b967fd0 2 parents f01c20d + de7e1e1
Elaine Tsai etsai authored
8 README.md
View
@@ -1,3 +1,7 @@
-# Twilio Workshop
+# TwilioCon Workshop
-Check out the [html version](http://twilio-workshop-cal.readthedocs.org/en/latest) of the guide here.
+Hello and welcome to TwilioCon Workshop Day
+
+You'll want to open the [workshop
+guide](http://twiliocon-workshop.readthedocs.org/en/latest/), which contains
+all the information for the rest of the day.
44 docs/browser.rst
View
@@ -63,7 +63,7 @@ Here is the function we'll use for generating the Capability Token.
from twilio.util import TwilioCapability
- def gen_token(account_sid, auth_token, application_sid):
+ def generate_token(account_sid, auth_token, application_sid):
capability = TwilioCapability(account_sid, auth_token)
# Allow access to the Call-in ApplicationSid we created
capability.allow_client_outgoing(application_sid)
@@ -74,17 +74,23 @@ Answering Queues in the Browser
-------------------------------
The first thing we'll need to build is a web interface. Let's start by adding a
-new AppEngine RequestHandler into ``main.py``.
+new AppEngine RequestHandler into ``main.py``.
+
+We have included some helper functions
+for generating `Capability Tokens <https://www.twilio.com/docs/client/capability-tokens>`_ and
+rendering templates on AppEngine. Those are imported from ``util.py``.
.. code-block:: python
+ from util import gen_token, render_template
+
class IndexPage(webapp2.RequestHandler):
def get(self):
params = {
- "token": gen_token(ACCOUNT_SID, AUTH_TOKEN, APP_SID)
+ "token": generate_token(ACCOUNT_SID, AUTH_TOKEN, APP_SID)
}
- self.response.out.write(render_template("index.html", params))
+ self.response.out.write(util.render_template("index.html", params))
Here is the ``index.html`` file we are rendering.
@@ -184,10 +190,10 @@ current call, and dial the next person in the queue.
<?xml version="1.0" encoding="UTF-8"?>
<Response>
- <Dial finishOnKey="#">
+ <Dial hangupOnStar="true">
<Queue>radio-callin-queue</Queue>
</Dial>
- <Redirect></Redirect>
+ <Redirect method="GET"></Redirect>
</Response>
Change your application's Voice URL so it serves this TwiML when dialed.
@@ -196,8 +202,8 @@ Getting the Next Caller From the <Queue>
-----------------------------------------
We want to make it easy to hangup the current call and move to the next one by
-pressing the "#" key on the phone. Twilio Client has a feature for sending DTMF
-tones (the tone when you press "#" on your phone) programmatically.
+pressing the "*" key on the phone. Twilio Client has a feature for sending DTMF
+tones (the tone when you press "*" on your phone) programmatically.
First, we need to hold on to the response of ``Twilio.Device.connect()`` so
let's add a global variable called ``connection`` and have every ``call()``
@@ -217,19 +223,19 @@ Now, we can add a new function, called ``next()``:
function next() {
if (connection) {
- connection.sendDTMF("#");
+ connection.sendDigits("*");
}
}
-Because we added a `finishOnKey` attribute to our TwiML, sending a "#" symbol
+Because we added a `hangupOnStar` attribute to our TwiML, sending a "*" symbol
via DTMF tone will hang up on the current caller, and connect the browser to
-the next caller.
+the next caller.
Now we just need to add another button to trigger the hangup.
.. code-block:: html
- <button class="next" onclick="next();">
+ <button class="hangup" onclick="next();">
Next Caller
</button>
@@ -247,11 +253,19 @@ JSON.
class QueueStatusPage(webapp2.RequestHandler):
- queue_sid = "QQ123"
def get(self):
client = TwilioRestClient(ACCOUNT_SID, AUTH_TOKEN)
- q_data = {"queues": client.queues.get(queue_sid)}
- self.response.out.write(json.dumps(q_data))
+
+ queue = [x for x in client.queues.list() if x.friendly_name == 'radio-callin-queue']
+
+ if queue:
+ q_data = {
+ "current_size": queue[0].current_size,
+ 'average_wait_time': queue[0].average_wait_time,
+ }
+ self.response.out.write(json.dumps(q_data))
+ else:
+ self.abort(404)
Add this QueueStatusPage into the WSGIApplication's routing map as
21 docs/callin.rst
View
@@ -103,7 +103,7 @@ We can spice up our TwiML endpoint by adding some wait music, using the
<?xml version="1.0" encoding="UTF-8"?>
<Response>
<Say>You are being enqueued now.</Say>
- <Enqueue waitUrl="/twiml/wait" waitMethod="GET">
+ <Enqueue waitUrl="/twiml/wait" waitUrlMethod="GET">
radio-callin-queue
</Enqueue>
</Response>
@@ -126,7 +126,7 @@ including ``<Say>`` and ``<Play>``.
resp = twiml.Response()
resp.say("You are being enqueued now.")
resp.enqueue("radio-callin-queue",
- waitUrl="/twiml/wait", waitMethod="GET")
+ waitUrl="/twiml/wait", waitUrlMethod="GET")
self.response.write(str(resp))
app = webapp2.WSGIApplication([
@@ -200,7 +200,7 @@ You will want to create a second Twilio Application for your DJ number, and
configure that application's Voice URL to point to the TwiML above.
.. code-block:: python
- :emphasize-lines: 14-24, 28
+ :emphasize-lines: 14-24, 27
import webapp2
from twilio import twiml
@@ -271,8 +271,11 @@ CurrentQueueSize The current number of enqueued calls in this queue.
Utilizing this information, we can inform our users what position they are in
the queue and how long they can expect to wait before an answer.
+Remember to change the ``waitUrlMethod`` from ``GET`` to ``POST`` now that we are using
+``POST`` data for the ``waitUrl``
+
.. code-block:: python
- :emphasize-lines: 9-16
+ :emphasize-lines: 12, 15-22
import webapp2
from twilio import twiml
@@ -280,14 +283,20 @@ the queue and how long they can expect to wait before an answer.
class EnqueueHandler(webapp2.RequestHandler):
def get(self):
- # Same as above
+ self.response.headers['Content-Type'] = 'application/xml'
+
+ resp = twiml.Response()
+ resp.say("You are being enqueued now.")
+ resp.enqueue("radio-callin-queue",
+ waitUrl="/twiml/wait", waitUrlMethod="POST")
+ self.response.write(str(resp))
class WaitHandler(webapp2.RequestHandler):
def post(self):
response = twiml.Response()
response.say("You are number %s in line." % self.request.get('QueuePosition'))
response.say("You've been in line for %s seconds." % self.request.get('QueueTime'))
- response.say("The average wait time is currently %s seconds." % self.request.get('AverageQueueTime'))
+ response.say("The average wait time is currently %s seconds." % self.request.get('AvgQueueTime'))
response.play("http://com.twilio.music.rock.s3.amazonaws.com/nickleus_-_original_guitar_song_200907251723.mp3")
self.response.out.write(str(response))
4 docs/contents.rst.inc
View
@@ -26,6 +26,6 @@ Unlocking the Box
callin
browser
- queue
- callcenter
+ sms_feedback
+ recording
bestpractices
109 docs/gather_voicemail.rst
View
@@ -0,0 +1,109 @@
+.. _gather_voicemail:
+
+Routing Calls to Voicemail
+==========================
+
+Phone numbers are available all of the time, but your agents might not be. We
+don't want to put people on hold if there's no human available.
+
+Instead, if our call-in hotline is closed, let's ask people to leave
+a voicemail. We'll use a simple time-of-day heuristic to determine whether to
+place users in a queue, or to direct them to voicemail.
+
+We may also want to give people the option to leave a voicemail if there are
+currently long hold times for a real agent.
+
+Let's take a look at what that logic would look like.
+
+.. code-block:: python
+
+ import webapp2
+ from twilio import twiml
+ from datetime import datetime
+
+ UNACCEPTABLE_WAIT_TIME = 5
+
+ class EnqueueHandler(webapp2.RequestHandler):
+
+ def queue_closed(opening_hour=9, closing_hour=17):
+ now = datetime.now()
+ # Check if current time is before opening or after closing
+ return now.hour < opening_hour or now.hour > closing_hour
+
+ def get(self):
+ self.response.headers['Content-Type'] = 'application/xml'
+
+ resp = twiml.Response()
+ queue_sid = 'QU123'
+ client = TwilioRestClient(ACCOUNT_SID, AUTH_TOKEN)
+ queue = client.queues.get(queue_sid)
+ if queue.average_wait_time > UNACCEPTABLE_WAIT_TIME:
+ with resp.gather(num_digits=1, timeout=10, action="/voicemail") as r:
+ r.say("The wait time is currently {} minutes. Press 1 to
+ leave a voicemail, or stay on the line for
+ an agent.".format(queue.average_wait_time))
+
+ elif self.queue_closed():
+ # Redirect straight to voicemail, no need to press a button.
+ resp.say("The queue is currently closed. Please stay on the line
+ to leave a voicemail.")
+ resp.redirect('/voicemail')
+ self.response.write(str(resp))
+
+ resp.enqueue("radio-callin-queue",
+ waitUrl="/twiml/wait", waitMethod="GET")
+ self.response.write(str(resp))
+
+ class WaitHandler(webapp2.RequestHandler):
+ # Rest of the file is the same as the previous pages...
+
+If the average queue wait time is higher than some threshold, we listen for
+a key input, and redirect people to voicemail if they press a key. If the user
+does not press a key, they'll just fall through to the <Enqueue> verb at the
+bottom of the handler. If the queue is closed, we redirect straight to
+voicemail.
+
+Otherwise, the queue is open and wait times are short, so we place people in
+the call queue.
+
+We need to add a new handler for our voicemail endpoint. Set up the following
+route to listen at ``/voicemail``.
+
+.. code-block:: python
+
+ import webapp2
+ from twilio import twiml
+ from datetime import datetime
+
+ UNACCEPTABLE_WAIT_TIME = 5
+
+ class EnqueueHandler(webapp2.RequestHandler):
+ # Same as above..
+
+ class VoicemailHandler(webapp2.RequestHandler):
+
+ def get(self):
+ """ GET /voicemail """
+ self.response.headers['Content-Type'] = 'application/xml'
+
+ resp = twiml.Response()
+ resp.say("Please leave a message after the tone.")
+ resp.record()
+ self.response.write(str(resp))
+
+ class WaitHandler(webapp2.RequestHandler):
+ # Rest of the file is the same as the previous pages...
+
+Try it out! You should be able to retrieve these recordings from your `Twilio
+Dashboard`_.
+
+Advanced Features
+------------------
+
+That's the end of the content for this tutorial. If you still have some time,
+try implementing some of these advanced features:
+
+- Send an email to yourself when someone leaves a new recording.
+- Write a unit test to check the logic in your controller.
+
+.. _Twilio Dashboard: https://www.twilio.com/user/account/log/recordings
58 docs/recording.rst
View
@@ -0,0 +1,58 @@
+.. _recording:
+
+Call Recording
+==============
+
+To help our DJ improve his manners on the phone, let's record all of the
+incoming calls to our Queue.
+
+Many states have laws about letting the party know that they are being
+recorded. So let's add a short message before the call, letting the calling
+party know that we are recording the call.
+
+We need to alter the TwiML that plays on the DJ's side to `add a url attribute`_.
+This URL will hold TwiML telling the caller they are about to be recorded.
+We're also going to add ``record="True"`` to the Dial verb, to record the call.
+
+.. code-block:: xml
+ :emphasize-lines: 3,4
+
+ <?xml version="1.0" encoding="UTF-8"?>
+ <Response>
+ <Dial record="true" hangupOnStar="true">
+ <Queue url="/record-message">radio-callin-queue</Queue>
+ </Dial>
+ <Redirect></Redirect>
+ </Response>
+
+Then at the ``/record-message`` route, place the following TwiML:
+
+.. code-block:: xml
+
+ <?xml version="1.0" encoding="UTF-8"?>
+ <Response>
+ <Say>This call is being recorded.</Say>
+ </Response>
+
+.. _add a url attribute: http://www.twilio.com/docs/api/twiml/queue#attributes-url
+
+That's it! Now all of your incoming calls will be recorded. To listen to the
+recorded calls, go to the `Recordings page of your Twilio Dashboard`_.
+
+.. _Recordings page of your Twilio Dashboard: https://www.twilio.com/user/account/log/recordings
+
+Advanced Features
+------------------
+
+That's the end of the content for this tutorial. If you still have some time,
+try implementing some of these advanced features:
+
+- Listen to your recordings straight from your Dashboard.
+- Export your recordings to a private S3 folder, and delete the Twilio copies
+ of the recording.
+- Set up monitoring of your application, so that you receive notifications if
+ your application becomes unreachable.
+- Set up HTTP Authentication so that Twilio must authenticate before reading
+ TwiML from your server, and other browsers will receive a 401 Forbidden
+ message.
+
4 docs/sms_feedback.rst
View
@@ -3,7 +3,7 @@
SMS Feedback
============
-We've set up a basic call in line. Let's say we want to keep the dialogue going
+We've set up a basic call-in line. Let's say we want to keep the dialogue going
with our callers a little more. We are going to send an SMS to callers after
they get off the line.
@@ -76,7 +76,7 @@ our SMS messages.
}
var getSMSMessages = function() {
- $.getJSON('messages.json', function(result) {
+ $.getJSON('/inbound-sms', function(result) {
var msgs = result.messages;
for (var i = 0; i < msgs.length; i++) {
var msg = msgs[i];
6 templates/index.html
View
@@ -2,6 +2,10 @@
<html>
<head>
<title>Radio Call-In Dashboard</title>
+ <link rel="shortcut icon" href="//static0.twilio.com/packages/favicons/img/Twilio_64.png" />
+ <link rel="apple-touch-icon" href="//static1.twilio.com/packages/favicons/img/Twilio_57.png" />
+ <link rel="apple-touch-icon" sizes="72x72" href="//static0.twilio.com/packages/favicons/img/Twilio_72.png" />
+ <link rel="apple-touch-icon" sizes="114x114" href="//static0.twilio.com/packages/favicons/img/Twilio_114.png" />
<script type="text/javascript"
src="http://static.twilio.com/libs/twiliojs/1.0/twilio.min.js"></script>
<script type="text/javascript"
@@ -26,7 +30,7 @@
});
function call() {
- Twilio.evice.connect();
+ Twilio.Device.connect();
}
</script>
</head>
Please sign in to comment.
Something went wrong with that request. Please try again.