using Python with Twilio how can I implement reject() and callback? - python

I want to call my Twilio number from my cellphone, Twilio recognizes my number, rejects the call and then calls me back. Here is the code:
#application.route("/test", methods=['GET', 'POST'])
def test():
whitelist = ['81808730xxxx', '44753810xxxx', '+44753810xxxx', '+44792834xxxx', '44792834xxxx']
call_from = request.values.get('From', 'None')
if call_from in whitelist:
# "<Response><Reject /></Response>"
resp = twilio.twiml.Response()
resp.reject()
time.sleep( 10 )
account_sid = "account_sid"
auth_token = "account_auth_token"
client = TwilioRestClient(account_sid, auth_token)
call = client.calls.create(to=call_from, from_="+1646480xxxx", url="https://zshizuka.herokuapp.com/gather")
print call.sid
else:
resp = twilio.twiml.Response()
call_to = "+44792834xxxx"
resp.dial(call_to)
#return json.dumps({'success':True}), 200
return str(resp)
This produces the dreaded response: "We are sorry a system error has occurred. Goodbye".
If, however, I dial the number and hangup after the first ring it works perfectly. So I am assuming that the problem is with the reject verb. As you can see I have tried with and without twiml.
I would like to get a busy signal, call rejected (so no cost to cell phone bill) and then callback.
Grateful for help.

I'd love to better understand your use case but I'm going to give this a go using the Twilio Python Helper Library.
As far as <Reject> TwiML goes, you can do something like this and be sure to specify a reason setting it to 'busy':
from flask import Flask
from flask import request
from twilio import twiml
account_sid = "YOU_ACCOUNT_SID"
auth_token = "YOUR_ACCOUNT_TOKEN"
client = TwilioRestClient(account_sid, auth_token)
app = Flask(__name__)
#app.route('/test', methods=["GET", "POST"])
def test():
whitelist = ['+14151XXXXXX', '+14152XXXXXX']
call_from = request.form['From']
if call_from in whitelist:
resp = twiml.Response()
resp.reject(reason="busy")
return str(resp)
else:
resp = twiml.Response()
call_to = "+14153XXXXXX"
resp.dial(call_to)
return str(resp)
if __name__ == "__main__":
app.run()
As there is no callback within <Reject> you will have a few options as to how you can proceed.
If you're using Python 3.3 or greater you might consider doing the whole process with asyncio.
With older versions of Python, then I would recommend you try completing the client call through a task queue as demonstrated in this post.
Please let me know if this helps at all.

Related

How to make Twilio Queue Automation Forward

Can someone tell me please: how to take callers, from enqueue to dial(forwarding). In one app, with automation this ???
i have something like that and it wont work:
<Say>hello</Say>
<Enqueue waitUrl="https://brass-dragonfly-1957.twil.io/assets/poczekalnia.xml">support</Enqueue>
<Dial url="/ivr/agent/screencall">
+000000000
<Queue>support</Queue>
</Dial>
<Redirect>/ivr/welcome/</Redirect>
</Response>
in python look like this:
twiml_response.say('hello')
twiml_response.enqueue('support', wait_url='https://brass-dragonfly-1957.twil.io/assets/poczekalnia.xml')
twiml_response.dial('+000000000', url=reverse('ivr:agents_screencall')).queue('support')
It looks like you are trying to perform actions for both your caller and an agent within the one TwiML response, and that will not work.
When you <Enqueue> a caller, no following TwiML will execute until you dequeue the caller with a <Leave>.
It looks like you want to dial an agent, allow them to screen the call and then connect them to the caller in the queue. To do this, you would start by creating a call to your agent using the REST API. With that call you would provide a URL that will be requested when that agent connects. In the response to that URL you should say the message to them, then <Dial> the <Queue>. Something like this:
import os
from twilio.rest import Client
account_sid = os.environ['TWILIO_ACCOUNT_SID']
auth_token = os.environ['TWILIO_AUTH_TOKEN']
client = Client(account_sid, auth_token)
def call(request):
twiml_response = VoiceResponse()
twiml_response.say('hello')
twiml_response.enqueue('support', wait_url='https://brass-dragonfly-1957.twil.io/assets/poczekalnia.xml')
call = client.calls.create(
url = '/agent'.
to = agent_phone_number,
from = your_twilio_number
)
return HttpResponse(twiml_response, content_type='text/xml')
Then, in response to the webhook to the /agent endpoint, you should return your response for screening, which might look like this:
def agent(request):
twiml_response = VoiceResponse()
gather = Gather(action='/agent_queue', method='POST', numDigits=1)
gather.say('You are receiving an incoming call, press 1 to accept')
twiml_response.append(gather)
return HttpResponse(twiml_response, content_type='text/xml')
And finally in /agent_queue you determine the result of screening the call and if the agent accepts, then you connect them to the queue.
def agent_queue(request):
twiml_response = VoiceResponse()
digits = request.POST.get("Digits", "")
if digits == "1":
dial = Dial()
dial.queue('support')
twiml_response.append(dial)
else:
twiml_response.hangup()
return HttpResponse(twiml_response, content_type='text/xml')

Twilio's Python WhatsApp API doesn't work with conditional flow statements based on responses

I'm experimenting with Twilio's WhatsApp API with Python but the conditional flow doesn't work. When I enter the word 'office' after getting the response 'What would you like to print?', I don't get the expected response 'Opening...'. I'd require some help regarding this.
from twilio.rest import Client
from flask import Flask, request, redirect
from twilio.twiml.messaging_response import MessagingResponse
app = Flask(__name__)
#app.route("/", methods=['GET', 'POST'])
def wa_print():
"""Respond to incoming messages with a simple text message."""
question = request.values.get('Body')
print(question)
# Start our TwiML response
resp = MessagingResponse()
if (question == 'print'):
resp.message('What would you like to print?')
doc = request.values.get('Body')
if (doc == 'office'):
resp.message('Opening...')
return str(resp)
if __name__ == "__main__":
app.run()
Twilio developer evangelist here.
Each time a user sends your WhatsApp number a message you will get a new request to your webhook endpoint. The new response will have the user's new message in the Body parameter.
In your example code you are always working with one response, so the variables question and doc are the same.
def wa_print():
"""Respond to incoming messages with a simple text message."""
question = request.values.get('Body')
print(question)
# Start our TwiML response
resp = MessagingResponse()
if (question == 'print'):
resp.message('What would you like to print?')
doc = request.values.get('Body')
if (doc == 'office'):
resp.message('Opening...')
return str(resp)
Instead of nesting your conditionals, you could flatten them like this:
def wa_print():
"""Respond to incoming messages with a simple text message."""
question = request.values.get('Body')
print(question)
# Start our TwiML response
resp = MessagingResponse()
if (question == 'print'):
resp.message('What would you like to print?')
elif (question == 'office'):
resp.message('Opening...')
return str(resp)
With the above code, if someone messages "print" your application will respond "What would you like to print?" and if they message "office" the application will respond with "Opening...".
If you want to get more into controlling the conversation you can store state in HTTP cookies. I recommend checking this article on creating SMS conversation in Python to learn about that in more depth.
Let me know if this helps at all.

TypeError: encoding without a string argument on twitter account activity api

I am setting up a flask server that will act as a webhook to the twitter account activity api. However I came up with this issue that I have no idea how to solve, I'm fairly new to programming so please bear with me. I just used this repository https://github.com/RickRedSix/twitter-webhook-boilerplate-python/blob/master/Main.py
This is the error:
line 28, in twitterCrcValidation
key=bytes(CONSUMER_SECRET, encoding ='utf-8'),
TypeError: encoding without a string argument
Here's the code:
#!/usr/bin/env python
from flask import Flask, request, send_from_directory, make_response
from http import HTTPStatus
import Twitter, hashlib, hmac, base64, os, logging, json
from dotenv import load_dotenv
load_dotenv('.env')
CONSUMER_SECRET = os.getenv('CONSUMER_SECRET')
CURRENT_USER_ID = os.getenv('CURRENT_USER_ID')
app = Flask(__name__)
#generic index route
#app.route('/')
def default_route():
return send_from_directory('www', 'index.html')
#The GET method for webhook should be used for the CRC check
#TODO: add header validation (compare_digest https://docs.python.org/3.6/library/hmac.html)
#app.route("/webhook", methods=["GET"])
def twitterCrcValidation():
crc = request.args['crc_token']
validation = hmac.new(
key=bytes(CONSUMER_SECRET, encoding ='utf-8'),
msg=bytes(crc, encoding = 'utf-8'),
digestmod = hashlib.sha256
)
digested = base64.b64encode(validation.digest())
response = {
'response_token': 'sha256=' + format(str(digested)[2:-1])
}
print('responding to CRC call')
return json.dumps(response)
#The POST method for webhook should be used for all other API events
#TODO: add event-specific behaviours beyond Direct Message and Like
#app.route("/webhook", methods=["POST"])
def twitterEventReceived():
requestJson = request.get_json()
#dump to console for debugging purposes
print(json.dumps(requestJson, indent=4, sort_keys=True))
if 'favorite_events' in requestJson.keys():
#Tweet Favourite Event, process that
likeObject = requestJson['favorite_events'][0]
userId = likeObject.get('user', {}).get('id')
#event is from myself so ignore (Favourite event fires when you send a DM too)
if userId == CURRENT_USER_ID:
return ('', HTTPStatus.OK)
Twitter.processLikeEvent(likeObject)
elif 'direct_message_events' in requestJson.keys():
#DM recieved, process that
eventType = requestJson['direct_message_events'][0].get("type")
messageObject = requestJson['direct_message_events'][0].get('message_create', {})
messageSenderId = messageObject.get('sender_id')
#event type isnt new message so ignore
if eventType != 'message_create':
return ('', HTTPStatus.OK)
#message is from myself so ignore (Message create fires when you send a DM too)
if messageSenderId == CURRENT_USER_ID:
return ('', HTTPStatus.OK)
Twitter.processDirectMessageEvent(messageObject)
else:
#Event type not supported
return ('', HTTPStatus.OK)
return ('', HTTPStatus.OK)
if __name__ == '__main__':
# Bind to PORT if defined, otherwise default to 65010.
port = int(os.environ.get('PORT', 65010))
gunicorn_logger = logging.getLogger('gunicorn.error')
app.logger.handlers = gunicorn_logger.handlers
app.logger.setLevel(gunicorn_logger.level)
app.run(host='0.0.0.0', port=port, debug=True)
You need to verify input to your program and environment variables are no exception. As a minumum, check that these variables actually exist. os.getenv returns None if the environment variable doesn't exist. None is not a string and thus your error. You could do this with a slightly different os call.
CONSUMER_SECRET = os.environ['CONSUMER_SECRET']
CURRENT_USER_ID = os.environ['CURRENT_USER_ID']
Now an exception is raised on failure. This could be wrapped in an exception handler if you want different error reporting than the standard traceback.

I got this error 'TypeError: can only concatenate str (not "NoneType") to str'

What I am trying to achieve is that the user will first ask the bot a question. Let say the user wants to find the nearest money changer he/she will type "I need to find a money changer. Then the bot will reply with 'Please provide the location". Once the user provides the coordinates the bot will then reply with all the nearby locations money changer.
from flask import Flask, request
import requests
from twilio.twiml.messaging_response import MessagingResponse
app = Flask(__name__)
#app.route('/sms', methods=['POST'])
def bot():
incoming_msg = request.values.get('Body', '').lower()
resp = MessagingResponse()
msg = resp.message()
if 'moneychanger' in incoming_msg:
search1 = 'Please provide the location please'
msg.body(search1)
message_latitude = request.values.get('Latitude', None)
message_longitude = request.values.get('Longitude', None)
responded = True
if message_latitude == None:
location = '%20' + message_latitude + '%2C' + message_longitude
responded = False
url = f'https://tih-api.stb.gov.sg/money-changer/v1?location={location}&radius=2000'
r = requests.get(url)
if r.status_code == 200:
data = r.json()
search = data['data'][0]['name']
else:
search = 'I could not retrieve a quote at this time, sorry.'
msg.body(search)
responded = True
return str(resp)
if __name__ == "__main__":
app.run(debug=True)
Twilio developer evangelist here.
I believe you are working with the Twilio API for WhatsApp, based on your using location parameters.
The issue here is that you are trying to reply to and receive more information within the same webhook request. However, the text message (with "moneychanger" in it) will come in a different request to the request with the location message. So, you need to store some state within your application that says your user is currently looking for a moneychanger.
Here's an example using Flask Sessions to store the incoming message and then ask for location and if there's a message put that together with the message and respond:
from flask import Flask, request, session
import requests
from twilio.twiml.messaging_response import MessagingResponse
app = Flask(__name__)
# Set the secret key to some random bytes. Keep this really secret!
# Don't use these bytes because they are in the documentation.
app.secret_key = b'_5#y2L"F4Q8z\n\xec]/'
#app.route('/sms', methods=['POST'])
def bot():
incoming_msg = request.values.get('Body', '').lower()
resp = MessagingResponse()
msg = resp.message()
message_latitude = request.values.get('Latitude', None)
message_longitude = request.values.get('Longitude', None)
if 'moneychanger' in incoming_msg:
# We're looking for a moneychanger, ask for the location
response = 'Please provide the location please'
session['message'] = incoming_msg
elif message_latitude && message_longitude && 'message' in session && 'moneychanger' in session['message']
# We have the location and the previous message was asking for a
# moneychanger.
location = '%20' + message_latitude + '%2C' + message_longitude
url = f'https://tih-api.stb.gov.sg/money-changer/v1?location={location}&radius=2000'
r = requests.get(url)
if r.status_code == 200:
data = r.json()
response = data['data'][0]['name']
else:
response = 'I could not retrieve a quote at this time, sorry.'
# we're done with the original message so we can unset it now.
session['message'] = None
else:
# In this case, either you have a message that doesn't include
# 'moneychanger' or you have latitude and longitude in the request but
# no session['message']. You probably want to do something else here, but
# I don't know what yet.
response = 'I\'m not sure what you\'re looking for.'
msg.body(response)
return str(resp)
if __name__ == "__main__":
app.run(debug=True)
You may also want to extend this so that if you receive a message with location in before receiving the request ('moneychanger') then you could store the location in the session and then ask what the user is looking for.
Let me know if that helps at all.

Initiate a call, record it, and play the recording back

I am putting together a POC for a client that wants to do phone based testing. In the POC, we simply want to let the user enter a phone# on a web page. We would then display a question and call their number. We would record their response to the question and play it back to them.
I can initiate the call, but can't figure out how to indicate that I want to record it. Ideally, I would like to say something and start recording after the beep.
I have all of 3 hours of experience with Twilio, so forgive my ignorance.
Here is my code so far:
import logging
# [START imports]
from flask import Flask, render_template, request
import twilio.twiml
from twilio.rest import TwilioRestClient
# [END imports]
app = Flask(__name__)
# [START form]
#app.route('/form')
def form():
return render_template('form.html')
# [END form]
# [START submitted]
#app.route('/submitted', methods=['POST'])
def submitted_form():
phone = request.form['phone']
account_sid = "AC60***********************"
auth_token = "27ea************************"
client = TwilioRestClient(account_sid, auth_token)
call = client.calls.create(to=phone, # Any phone number
from_="+160#######", # Must be a valid Twilio number
url="https://my-host/static/prompt.xml")
call.record(maxLength="30", action="/handle-recording")
return render_template(
'submitted_form.html',
phone=phone)
# [END render_template]
#app.route("/handle-recording", methods=['GET', 'POST'])
def handle_recording():
"""Play back the caller's recording."""
recording_url = request.values.get("RecordingUrl", None)
resp = twilio.twiml.Response()
resp.say("Thanks for your response... take a listen to what you responded.")
resp.play(recording_url)
resp.say("Goodbye.")
return str(resp)
#app.errorhandler(500)
def server_error(e):
# Log the error and stacktrace.
logging.exception('An error occurred during a request.')
return 'An internal error occurred.', 500
# [END app]
Twilio developer evangelist here.
When you create the call, you pass a URL to the call. That URL will be the one called when the user answers the phone. The response to that request should be the TwiML to instruct Twilio to say the message and record the response. Like so:
#app.route("/handle-call", methods=['GET', 'POST'])
def handle_call():
resp = twilio.twiml.Response()
resp.say("Please leave your message after the beep")
resp.record(action="/handle-recording", method="POST")
return str(resp)
Then you just need to update your call creation to point to that URL
call = client.calls.create(to=phone, # Any phone number
from_="+160#######", # Must be a valid Twilio number
url="https://my-host/handle-call")
Your /handle-recording path looks as though it will do what you want already.
Just a quick tip, as you're new to Twilio, when developing using webhooks I recommend using ngrok to tunnel to your dev machine and expose your application to Twilio. I wrote a blog post about how to use ngrok and some of the features I like too.
Let me know if this helps at all.

Categories