Reputation: 1176
I am building a nested Twilio answering machine/app in python with flask and mod_wsgi. I have it working somewhat, in that if you type any number besides 3 it says not so much and prompts you again. 3 takes you to the next function, but inputted numbers respond as if still on the first gather function. What am I missing? Thanks!
from .customer_details import *
from flask import Flask, request
from twilio.twiml.voice_response import VoiceResponse, Gather
import os,sys
from twilio.rest import Client
global request, v, resp
v = "Polly.Ayanda-Neural"
def listener():
global request, v, resp
Call = "SmsSid" not in request.form.keys()
resp = VoiceResponse()
sender = request.form['From']
if Call:
if sender==sender: #actual code checks for valid phone
resp = VoiceResponse()
t="hi"
resp.say(t, voice=v)
else:
resp.say("Goodbye")
return str(resp)
print(getmerged2())
elif not Call:
message_body = request.form['Body']
pass
return str(resp)
def getmerged2():
global request, v, resp
if 'Digits' in request.values:
choice = request.values['Digits']
if choice == '3':
resp = VoiceResponse()
resp.say("bonjour", voice=v)
waitforagent()
else:
resp=VoiceResponse()
resp.say("not so much", voice=v)
with resp.gather(numDigits=1, timeout="60") as g:
g.say("press a button")
return resp
def waitforagent2():
global request, v, resp
if len(request.values['Digits']) >1:
choice = request.values['Digits'][:1]
if choice == '3':
resp = VoiceResponse()
resp.say("Wahoo")
else:
resp = VoiceResponse()
resp.say ("BOOHOOO")
with resp.gather (numDigits=1, timeout="60") as g:
g.say ("select an option")
Upvotes: 1
Views: 843
Reputation: 73100
I think your issue here is likely to do with global
variables. Which I will come back to. First I want to explain how you might do this better with TwiML.
It's important to note that every time you return a response with a <Gather>
in it to Twilio, Twilio makes a new webhook request to your application when it gets an input from the user. The conversation is a series of requests from Twilio to your application and responses from your application to Twilio to say what to do next.
With <Gather>
when you get the caller's response Twilio will, by default, call the same URL this time with the Digits
or SpeechResult
parameter present. You look to have handled that a bit in your existing code, however it also appears that you expect the code to continue running. Instead, each new request from Twilio to your endpoint will start at the top of your code again.
I find it is easier to break things apart and handle results with different code by using a different endpoint. To do so, you can pass a URL as the action
attribute in the <Gather>
. The documentation specifically calls this out:
Without an
action
URL, Twilio will re-request the URL that hosts the TwiML you just executed. This can lead to unwanted looping behavior if you're not careful. See our example below for more information.
So, if you use an action
attribute and pass a URL to a different endpoint you can handle the result in a different part of the code to handling the initial message and <Gather>
,.
Check out this example from the documentation for how to handle the incoming request and then gather result with different endpoints:
@app.route("/voice", methods=['GET', 'POST'])
def voice():
"""Respond to incoming phone calls with a menu of options"""
# Start our TwiML response
resp = VoiceResponse()
# Start our <Gather> verb
gather = Gather(num_digits=1, action='/gather')
gather.say('For sales, press 1. For support, press 2.')
resp.append(gather)
# If the user doesn't select an option, redirect them into a loop
resp.redirect('/voice')
return str(resp)
@app.route('/gather', methods=['GET', 'POST'])
def gather():
"""Processes results from the <Gather> prompt in /voice"""
# Start our TwiML response
resp = VoiceResponse()
# If Twilio's request to our app included already gathered digits,
# process them
if 'Digits' in request.values:
# Get which digit the caller chose
choice = request.values['Digits']
# <Say> a different message depending on the caller's choice
if choice == '1':
resp.say('You selected sales. Good for you!')
return str(resp)
elif choice == '2':
resp.say('You need support. We will help!')
return str(resp)
else:
# If the caller didn't choose 1 or 2, apologize and ask them again
resp.say("Sorry, I don't understand that choice.")
# If the user didn't choose 1 or 2 (or anything), send them back to /voice
resp.redirect('/voice')
return str(resp)
Back to those global variables though. In the top of your code, you import request from the Flask package and then you make it global. The request
object in each different Flask endpoint is a representation of the request being made to the web server. Making that global means that running any other Flask endpoint may well overwrite that variable everywhere in the program.
In your program I would definitely avoid making request
a global. You can make the voice v
a constant. And resp
should stay local to each endpoint too.
Here is your code rewritten to use different endpoints to deal with different inputs at different times:
from .customer_details import *
from flask import Flask, request
from twilio.twiml.voice_response import VoiceResponse, Gather
import os,sys
VOICE = "Polly.Ayanda-Neural"
@app.route("/voice", methods=['GET', 'POST'])
def listener():
global request, v, resp
Call = "SmsSid" not in request.form.keys()
resp = VoiceResponse()
sender = request.form['From']
if Call:
if sender==sender: #actual code checks for valid phone
resp = VoiceResponse()
t="hi"
resp.say(t, voice=VOICE)
else:
resp.say("Goodbye")
return str(resp)
# Add the first gather here, but give it an action URL
with resp.gather(numDigits=1, timeout="60", action="/gather1") as g:
g.say("press a button")
elif not Call:
message_body = request.form['Body']
pass
return str(resp)
@app.route("/gather1", methods=['GET', 'POST'])
def gather1(request, resp):
if 'Digits' in request.values:
choice = request.values['Digits']
if choice == '3':
resp.say("bonjour", voice=VOICE)
with resp.gather (numDigits=1, timeout="60", action="/gather2") as g:
g.say ("select an option")
else:
resp=VoiceResponse()
resp.say("not so much", voice=VOICE)
return str(resp)
@app.route("/gather2", methods=['GET', 'POST'])
def gather2():
if len(request.values['Digits']) >1:
choice = request.values['Digits'][:1]
if choice == '3':
resp = VoiceResponse()
resp.say("Wahoo")
else:
resp = VoiceResponse()
resp.say ("BOOHOOO")
return str(resp)
In the above code, the first endpoint is called first and returns a TwiML response that asks the caller to "press a button". When the user does press the button, Twilio then calls the second endpoint /gather1
. This checks if the Digit they pressed was 3. If it was, it asks them to "select an option". If it wasn't 3 then it responds "not so much" and then hangs up. Finally, if the user first pressed 3 and then presses another Digit, Twilio makes a request to the third endpoint /gather2
. This checks the Digit again, if it is 3 it responds with "Wahoo" and if not "BOOHOOO".
Upvotes: 1