I'm a university student relatively new to Python programming. I have been trying to build a conversational bot that allows a user to schedule appointments via SMS in Python.
I have been following many Python Twilio SMS tutorials online such as the official Twilio guide. I cannot figure out how to route a Twilio SMS response to the corresponding route. For example, a user is supposed to text in either SCHEDULE, RESCHEDULE, CHECK, or DROP, and the application is supposed to route them to the corresponding route with some logic to process the request (and reply with a further SMS message). If they do not enter one of the four specified words, the application is supposed to reroute the user back to the /inbound route to ask them to again clarify that they are selecting one of the four acceptable options. I have really been struggling hard for the past couple of days on this, getting confused on routes and whether or not to use the message = client.messages.create or response = MessagingResponse() Twilio APIs. I am using Flask to handle the web server capabilities.
Also, would you happen to know how to pass a parameter through the redirect route so that I can use that data in the next route and save it in a database?
Thank you so much. I greatly appreciate all the help I can get.
from twilio.twiml.messaging_response import Body, Message, Redirect, MessagingResponse
from flask import Flask, request, session
app = Flask(__name__)
# Main function triggered by a POST request by ngrok. When an SMS is received, Twilio will send the POST.
#app.route('/inbound', methods=['POST'])
def inbound():
""" Use Twilio API to reply to texts """
sender = request.form['From']
message = request.form['Body']
recipient = request.form['To'] # Recipient Phone Number
message_id = request.form['MessageSid'] # Twilio message id
response = MessagingResponse() # init a Twilio response
formatted = message.lower().strip() # formulate answer to message
if "schedule" == formatted:
reply = 'Hi, my name is Cletus. I\'d like to help you schedule your appointment today. What is your name? ' \
'Please format \"First Last\" (ex: John Smith).'
response.message(reply)
response.redirect('/schedule')
# write to db
elif "reschedule" == formatted:
reply = 'Hi, my name is Cletus. I\'d like to help you reschedule your appointment today. What is your name? ' \
'Please format \"First Last\" (ex: John Smith).'
response.message(reply)
response.redirect('/reschedule')
elif "appointments" == formatted:
reply = 'Hi, my name is Cletus. I\'d like to help you check your upcoming appointment. What is your name? ' \
'Please format \"First Last\" (ex: John Smith).'
response.message(reply)
response.redirect('/check')
elif "drop" == formatted:
reply = 'Hi, my name is Cletus. I\'d like to help you cancel your appointment. What is your name? ' \
'Please format \"First Last\" (ex: John Smith).'
response.message(reply)
response.redirect('/drop')
else:
reply = 'Hi, my name is Cletus. How can I help you? You can text RESCHEDULE to reschedule you appointment, ' \
'DROP to cancel your appointment, and APPOINTMENTS to receive a list of your currently scheduled ' \
'appointments. Have a great day!'
response.message(reply)
response.redirect('/inbound')
return str(response)
#app.route('/schedule', methods=['POST'])
def schedule():
print('Finally routing to schedule')
sender = request.form['From']
message = request.form['Body']
recipient = request.form['To'] # Recipient Phone Number
message_id = request.form['MessageSid'] # Twilio message id
response = MessagingResponse() # init a Twilio response
formatted = message.lower().strip()
#app.route('/reschedule', methods=['POST'])
def reschedule():
sender = request.form['From']
message = request.form['Body']
recipient = request.form['To'] # Recipient Phone Number
message_id = request.form['MessageSid'] # Twilio message id
response = MessagingResponse() # init a Twilio response
formatted = message.lower().strip()
#app.route('/check', methods=['POST'])
def check():
sender = request.form['From']
message = request.form['Body']
recipient = request.form['To'] # Recipient Phone Number
message_id = request.form['MessageSid'] # Twilio message id
response = MessagingResponse() # init a Twilio response
formatted = message.lower().strip()
#app.route('/drop', methods=['POST'])
def cancel():
sender = request.form['From']
message = request.form['Body']
recipient = request.form['To'] # Recipient Phone Number
message_id = request.form['MessageSid'] # Twilio message id
response = MessagingResponse() # init a Twilio response
formatted = message.lower().strip()
if __name__ == '__main__':
app.run(debug = True)
Related
I have a task to create a REST API that will be responsible for handling messages. As a part of the functionality, I should be able to write a message. This message should contain the following fields:
id
sender
receiver
message itself
subject
creation date
So, as I expected to do this is to have a route that should handle the object that I will send as an argument. But I am not sure I can do so. What would you recommend in this case?
For now, I can handle it somehow like this:
#app.route('/new_message/<string:sender>/<string:receiver>/<string:message>/', methods=['POST'])
def get_message(sender, receiver, message):
sender = sender
receiver = receiver
message = message
# Code that will add the data or to the database or to the json file
# if I decide not to implement DB for this task
return 'Sent successfully'
Thanks for your advice!
I am suggesting you to use JSON request body instead of the path parameters for the POST method.
Here is the example,
from flask import request, Flask
app = Flask(__name__)
#app.route('/new_message', methods=['POST'])
def get_message():
payload = request.json()
sender = payload['sender']
receiver = payload['receiver']
message = payload['message']
return 'Sent successfully'
if __name__ == "__main__":
app.run()
Now, If you want to add message as object then you can add it in JSON body. Not only message object you can add any number of fields if required.
I'm building a chatbot deployed via FB messenger. According to FB documentation, you have to send this post request to the messenger platform. I send a string representation of the payload using json.dumps. However, I never see the typing bubble and only get the actual response message from the bot. Here is the pertinent code from the Flask app.
#app.route('/', methods=['GET', 'POST'])
def receive_message():
global tag, latest_message
if request.method == 'GET':
# Before allowing people to message your bot Facebook has implemented a verify token
# that confirms all requests that your bot receives came from Facebook.
token_sent = request.args.get("hub.verify_token")
return verify_fb_token(token_sent)
# If the request was not GET, it must be POSTand we can just proceed with sending a message
# back to user
else:
# get whatever message a user sent the bot
output = request.get_json()
for event in output['entry']:
messaging = event['messaging']
for message in messaging:
if message.get('message'):
# Facebook Messenger ID for user so we know where to send response back to
recipient_id = message['sender']['id']
typing_payload = json.dumps({"recipient":{"id":recipient_id}, "sender_action":"typing_on"})
bot.send_raw(typing_payload)
time.sleep(3)
if message['message'].get('text'):
response_sent_text = send(message['message'].get('text'))
send_message(recipient_id, response_sent_text)
latest_message = response_sent_text
return "Message Processed"
You can use Pymessenger wrapper. Just import it, create an instance and pass your FB_TOKEN and wrap your actions. Remember when sending action, Facebook doc state that you must only pass the recipient_id and action and call the method separate from the response.
set action to "typing_on" in your method.
NB: Your method is incorrect because you are mixing sender_id and recipient_id
from pymessenger import Bot
bot = Bot(<FB_ACCESS_TOKEN>)
def send_typing_bubble(sender_id, recipient_id, action, response_sent_text):
bot.send_action(sender_id, action)
bot.send_text_message(sender_id, response_sent_text)
After your create the method you can just call send_typing_bubble() inside your webhook receive_message() method
if messaging_event.get('message'):
#your code here
send_typing_bubble(sender_id, recipient_id, action,
response_sent_text)
I'm trying to set my chatbot to display a typing bubble before sending the user the response so that it's more human like. I followed the documentation but am still getting instant responses from the bot with no typing bubble. As per the documentation I am using this sender action:
{"recipient":{"id":recipient_id}, "sender_action":"typing_on"}
And to put it in context, here is how I'm using it in the flask app:
#app.route('/', methods=['GET', 'POST'])
def receive_message():
global tag, latest_message
if request.method == 'GET':
# Before allowing people to message your bot Facebook has implemented a verify token
# that confirms all requests that your bot receives came from Facebook.
token_sent = request.args.get("hub.verify_token")
return verify_fb_token(token_sent)
# If the request was not GET, it must be POSTand we can just proceed with sending a message
# back to user
else:
# get whatever message a user sent the bot
output = request.get_json()
for event in output['entry']:
messaging = event['messaging']
for message in messaging:
if message.get('message'):
# Facebook Messenger ID for user so we know where to send response back to
recipient_id = message['sender']['id']
if message['message'].get('text'):
response_sent_text = send(message['message'].get('text'))
send_message(recipient_id, response_sent_text)
latest_message = response_sent_text
return "Message Processed"
def send_message(recipient_id, response):
# sends user the text message provided via input response parameter
typing_payload = {"recipient":{"id":recipient_id}, "sender_action":"typing_on"}
bot.send_raw(typing_payload)
print(bot.send_raw(typing_payload))
bot.send_text_message(recipient_id, response)
return "success"
Any ideas would be much appreciated!
You should pass a string representation of the payload (not a dictionary).
try doing something like this:
import json
...
def send_message(recipient_id, response):
# sends user the text message provided via input response parameter
typing_payload = json.dumps({"recipient":{"id":recipient_id}, "sender_action":"typing_on"})
bot.send_raw(typing_payload)
print(bot.send_raw(typing_payload))
bot.send_text_message(recipient_id, response)
return "success"
Also it's best practice to send these indicators upon receiving message (to show the typing indicator while processing), as opposed to directly before sending message.
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.
I'm trying to save both messages that get sent out and received in a database, including message sid, message body, so that I can process them using some logic.
class OutgoingMessage(models.Model):
''' Table of SMS messages that are sent out to users by Twilio '''
outgoing_sid = models.CharField(max_length=40)
sent_date = models.DateTimeField()
sender = models.ForeignKey(TwilioNumber, related_name='outgoing_messages')
recipient = models.ForeignKey(Caller, related_name='outgoing_messages')
message_body = models.TextField(max_length=1600)
class IncomingMessage(models.Model):
''' Table of SMS messages received by Twilio from users '''
incoming_sid = models.CharField(max_length=40)
sent_date = models.DateTimeField()
sender = models.ForeignKey(Caller, related_name='incoming_messages')
recipient = models.ForeignKey(TwilioNumber, related_name='incoming_messages')
message_body = models.TextField(max_length=1600)
Is there a straightforward way of getting the message sid from a message Twilio sends out, immediately after it gets sent? Getting the sid from an incoming message is pretty easy but the other way around is not very clear.
I'm trying to look for an alternative to using cookies, as suggested in this post
I found the answer here
# Download the Python helper library from twilio.com/docs/python/install
from twilio.rest import TwilioRestClient
# Your Account Sid and Auth Token from twilio.com/user/account
account_sid = "AC1ab0bb368322688c3d6cbb1355aeef9b"
auth_token = "{{ auth_token }}"
client = TwilioRestClient(account_sid, auth_token)
message = client.messages.create(body="Jenny please?! I love you <3",
to="+15558675309",
from_="+14158141829",
media_url="http://www.example.com/hearts.png")
print message.sid
However, this doesn't seem to work very well with django_twilio views.