HippoMan
HippoMan

Reputation: 2318

twilio/python/flask: call-forward but redirect to voicemail if no timely answer

I have written a flask app (using python3) which I have registered with twilio to respond to incoming calls to my twilio-supplied phone number. I can get it to route to voice mail, hang up, ignore the call, and forward to another phone number, which I am calling here the target number.

But there is one piece of functionality I haven't been able to figure out how to implement within this twilio/python/flask application. If the incoming number is not rejected by my application's logic, I want the call to be forwarded to the target number. But if no one answers that number within, say, 30 seconds, I want the call to be directed to the voicemail I have set up via twimlets. I don't know how to implement this conditional forward/voicemail logic here.

This has to happen within the answer_call method near the bottom of the following code:

import re

from flask import Flask, Response, redirect, request, send_file
from twilio.twiml.voice_response import VoiceResponse

app = Flask(__name__)

ngrokurl = 'http://XXXXXXXX.ngrok.io'
vmbase   = 'http://twimlets.com/[email protected]&Transcribe=False'
vmpath   = '/path/to/voicemessage.mp3'
vmurl    = os.path.join(ngrokurl, 'vm')
fwd      = '+1-111-111-11111'

def num(n):
    return re.compile(n)

whitelist = [
    num(r'^[+]11100000000$'),
    num(r'^[+]11111100000$'),
    num(r'^[+]11111111100$'),
]

ignorelist = [
    num(r'^[+]2000'),
]

hanguplist = [
    num(r'^[+]3000'),
]

def number_in(n, numlist):
    if not n or not numlist:
        return False
    for item in numlist:
        if item.search(n):
            return True
    return False

def voicemail():
    return redirect(
        '{}&Message={}'.format(vmbase, vmurl),
        302
    )

def ignore():
    resp = VoiceResponse()
    resp.play(vmurl, loop=1)
    return Response(str(resp), 200, mimetype='application/xml')

def hangup():
    resp = VoiceResponse()
    resp.hangup()
    return Response(str(resp), 200, mimetype='application/xml')

def forward():
    resp = VoiceResponse()
    resp.dial(fwd)
    return Response(str(resp), 200, mimetype='application/xml')

@app.route('/answer', methods=['GET', 'POST'])
def answer_call():
    '''
    Main flask route for answering phone calls.
    '''
    number = request.args.get('From', None)
    if not number:
        return hangup()
    if number_in(number, whitelist):
        # Whitelisted numbers are directly forwarded.
        #
        # However, what I want to do here is forward to
        # the target number, but then to redirect
        # to voicemail if the call isn't answered
        # after, say, 30 seconds. Don't know how ... ???
        #
        return forward()
##        return voicemail()
    elif number_in(number, ignorelist):
        return ignore()
    elif number_in(number, hanguplist):
        return hangup()
    else:
        #
        # Same question here: how do I forward
        # to the target but then redirect
        # to voicemail if the call isn't answered?
        #
        return forward()
##        return voicemail()

@app.route('/vm', methods=['GET', 'POST'])
def vm():
    '''
    URL for the voicemail recording used within the
    twimlets voicemail call. See `vmurl`, above.
    '''
    return send_file(vmpath, 'audio/mpeg')

if __name__ == "__main__":
    app.run(debug=True)

Thank you in advance for any pointers or suggestions that any of you could offer.

PS: I know there is documentation for how to do this outside of a python/flask environment. However, I haven't been able to figure out how to make this happen within python/flask.

Upvotes: 0

Views: 289

Answers (2)

HippoMan
HippoMan

Reputation: 2318

Thanks to Alan's help, I solved it. The code below replaces forward and answer_call from my initial example, and it adds a new call_result method. Everything else about my example stays the same.

Now, the REST-ful app will whitelist approved numbers and block unwanted numbers. Any approved or non-blocked number will be forwarded to the target phone number. If no one answers after 20 seconds, the app routes the call to my twimlet-implemented voicemail.

Obviously, this sample app contains hard-coded filters which simply contain dummy data. In real life, I will load these from a datastore every time a call comes in. This way, I can modify these filters from outside of the REST-ful app, without having to recompile or even restart that app.

So far, my initial coding is working well.

def forward():
    resp = VoiceResponse()
    resp.dial(
        fwd,
        action=url_for('call_result'),
        method='GET',
        timeout=20
    )
    return Response(str(resp), 200, mimetype='application/xml')

@app.route('/answer', methods=['GET', 'POST'])
def answer_call():
    number = request.args.get('From', None)
    if not number:
        return hangup()
    elif number_in(number, whitelist):
        return forward()
    elif number_in(number, ignorelist):
        return ignore()
    elif number_in(number, hanguplist):
        return hangup()
    else:
        return forward()

@app.route('/call', methods=['GET', 'POST'])
def call_result():
    status = request.args.get('DialCallStatus', None)
    if not status or status != 'completed':
        return voicemail()
    else:
        return Response(str(resp), 200, mimetype='application/xml')

Upvotes: 0

Alan
Alan

Reputation: 10781

You should define an Action URL for your verb for your forward() function. The DialCallStatus on the request to that Action URL (which would steer back to a URL of your Flask application) will then let you know if the call was successful or not. If the call was not successfully "completed", you would route to your VM logic, otherwise . The default timeout for a verb is 30 seconds, so you are good there.

The key logic is in the Function below, it should not be that difficult to port the logic over to Python.

Create Voicemail Actions in Functions https://www.twilio.com/docs/wireless/tutorials/communications-guides/implement-voicemail#create-voicemail-action-in-functions

So basically for the above function, you want to hang-up the call if the DialCallStatus is completed, otherwise route to VM/Twimlet.

Upvotes: 2

Related Questions