I travel a lot for both work and pleasure. My mom loves to know where I’m jetsetting off to and I was failing to keep her properly updated. I could share my location via Find my Friends, but that doesn’t solve the problem of upcoming travel events. I could create a shared document or calendar, but she isn’t always in front of a computer.
Enter the Where’s Kelley bot.
Using Twilio SMS, I hooked up a phone number to my Google calendar and created a simple text message bot that my mom can ask about my current location and upcoming travel schedule.
This post will walk through how to build one for yourself! Check out the final code here or follow along with this tutorial to build your own Text Travel Tracker from scratch.
Google Calendar API and Service Accounts
First things first, you’ll need a calendar to track. The way I’ve set this up is to have a separate calendar that I only add my travel schedule to and I recommend you do the same. To create a new calendar head to this page. Give your new calendar a name like Travel Tracker
and create the calendar.
While we’re at it, click on your new calendar and go to Integration Settings. Grab the Calendar ID and copy it; we’ll need this later. Add a couple events in your calendar: one that’s happening today and a few in the future.
To programmatically access your calendar, you’ll need to create a service account and OAuth2 credentials in the Google API Console. If you’ve been traumatized by OAuth2 development before, don’t worry; service accounts are way easier to use.
Follow along with the steps and video below. You’ll be in and out of the console in 60 seconds (much like Nic Cage in your favorite Nic Cage movie).
- Go to the Google APIs Console.
- Create a new project.
- Click Enable API. Search for and enable the Google Calendar API.
- Create credentials for a Web Server to access Application Data.
- Name the service account and grant it a Project Role of Editor.
- Download the JSON file.
- Copy the JSON file to your code directory and rename it to client_secret.json
https://www.twilio.com/blog/wp-content/uploads/2018/06/calendar-service-account-setup.mp4
There is one last required step to authorize your app, and it’s easy to miss!
Find the client_email
inside client_secret.json
. Back in your calendar settings, click “Share with specific people” and add that client_email by clicking ADD PEOPLE
.
Let’s write some code
To code along with this post, you’ll need to:
In an empty directory for your project, create a new file called requirements.txt
and add the following dependencies:
flask==1.0.2
google-api-python-client==1.7.3
oauth2client==4.1.2
twilio==6.14.4
The Google API python client provides a nice interface for talking to the Calendar API while the oauth2client helps us connect our Service Account to our code. We’ll be using Flask and Twilio to manage our text bot.
Set up your virtualenv and install the dependencies by running:
virtualenv env
source env/bin/activate
pip install -r requirements.txt
Create a file called app_config.py
and add the following code:
import os
CALENDAR_ID = os.environ["CALENDAR_ID"]
TRAVELER = "<Your name here>"
TWILIO_AUTH_TOKEN = os.environ["TWILIO_AUTH_TOKEN"]
TWILIO_ACCOUNT_SID = os.environ["TWILIO_ACCOUNT_SID"]
This expects that your Calendar ID and Twilio credentials are set as environment variables. We grabbed our Calendar ID earlier and you can find your Twilio credentials in the Console under Project Info
. For more information on how to set environment variables, check out this handy post.
Now that you have the dependencies taken care of, create a file called tracker.py
in the same directory as app_config.py
. This will be the code for the Flask app that will receive the messages.
Here’s the code you’ll need to parse your calendar and respond to SMS messages. Copy and paste it into tracker.py and save the file:
from oauth2client.service_account import ServiceAccountCredentials
from apiclient import discovery
from flask import Flask, Response, request
from datetime import datetime, timedelta
from twilio.rest import Client
from twilio.twiml.messaging_response import MessagingResponse
app = Flask(__name__)
app.config.from_object('app_config')
client = Client(app.config.get("TWILIO_ACCOUNT_SID"), app.config.get("TWILIO_AUTH_TOKEN"))
def _build_service():
scope = 'https://www.googleapis.com/auth/calendar.readonly'
credentials = ServiceAccountCredentials.from_json_keyfile_name('client_secret.json', scope)
service = discovery.build('calendar', 'v3', credentials=credentials)
return service
def where_you_at(service, calendar_id):
"""Find out if I'm traveling right now, respond with current location or the next trip if not currently traveling."""
now = datetime.utcnow()
today = now.isoformat() 'Z'
tomorrow = (now timedelta(days=1)).isoformat() 'Z'
# let google do the datetime math
currentEvent = service.events().list(
calendarId=calendar_id,
timeMin=today,
timeMax=tomorrow,
maxResults=1,
singleEvents=True,
orderBy='startTime'
).execute()
currentlyTravelingTo = currentEvent.get('items', [])
if not currentlyTravelingTo:
futureEvents = service.events().list(
calendarId=calendar_id,
timeMin=today,
maxResults=1,
singleEvents=True,
orderBy='startTime'
).execute()
event = futureEvents.get('items', [])[0]
start = event['start'].get('dateTime', event['start'].get('date'))
msg = "Doesn't look like {} is currently traveling. The next trip is to {} on {}".format(
app.config.get("TRAVELER"),
event['summary'],
start)
else:
event = currentlyTravelingTo[0]
end = event['end'].get('dateTime', event['end'].get('date'))
msg = "{} is currently in {} until {}".format(
app.config.get("TRAVELER"),
event['summary'],
end)
resp = MessagingResponse()
resp.message(msg)
return str(resp)
def _event_info(event):
start = event['start'].get('date')
end = event['end'].get('date')
summary = event['summary']
return "{} from {} to {}".format(summary, start, end)
def travel_schedule(service, calendar_id):
"""Look up the next 5 events in our calendar and format them as a message."""
now = datetime.utcnow()
today = now.isoformat() 'Z'
tomorrow = (now timedelta(days=1)).isoformat() 'Z'
# let google do the datetime math
event_list = service.events().list(
calendarId=calendar_id,
timeMin=today,
maxResults=5,
singleEvents=True,
orderBy='startTime'
).execute()
event_response = ["My next five planned events are:n"]
for event in event_list.get("items", []):
event_response.append(_event_info(event))
msg = "n".join(event_response)
resp = MessagingResponse()
resp.message(msg)
return str(resp)
def help_response():
"""The default response if the user texts in an unknown command."""
resp = MessagingResponse()
resp.message("Ask me 'Where is {}?' to see my current whereabouts or 'Travel schedule' to see what's coming up.".format(app.config.get("TRAVELER")))
return str(resp)
@app.route("/sms", methods=["GET", "POST"])
def main():
"""Respond to an incoming SMS message based on the body of the message."""
incoming_message = request.values.get("Body")
service = _build_service()
calendar_id = app.config.get("CALENDAR_ID")
normalized_message = incoming_message.lower()
if "where" in normalized_message:
return where_you_at(service, calendar_id)
elif "schedule" in normalized_message:
return travel_schedule(service, calendar_id)
else:
return help_response()
if __name__ == '__main__':
app.run(debug=True)
This code creates a Flask server with a single endpoint for responding to an incoming SMS message. We take a look at the body of the message and respond to the user in one of 3 ways:
- Current location
- Travel schedule
- Help / default response
If the message is asking for either our current location or travel schedule, we use the Google Calendar API to look up current and future events and format those as a friendly message back to the user. Otherwise we respond with a help response that tells the user what they can ask.
Start up the application by running python tracker.py
from your terminal.
Connecting Your Calendar to Twilio SMS
We’ll need to make our application accessible on a public port so that Twilio can connect to what’s running on your local computer. We’ll use a tool called ngrok for that.
Fire up ngrok
from a new terminal window or tab and point it to your Flask application, which will be running on Port 5000
ngrok http 5000
http://e978addb.ngrok.io
in this example, and head over to the configuration page for the Twilio phone number you purchased. Paste your ngrok url plus /sms
in your Twilio phone number configuration as a webhook for when A MESSAGE COMES IN
.
Grab your phone and send a text to your Twilio number and et voila:
Go Forth and Travel
I’ve added some special flavor to my own script, and I encourage you to do the same. You could restrict responses to a list of known numbers or support more incoming questions. I wasn’t sure how much use this bot would get, but it’s been a huge hit. My mom doesn’t feel guilty bothering me about my schedule and my friends have even used it to see when I’m in town.
Now that you are familiar with using Service Accounts, check out how to read data from a Google Sheet with Python. If you have any thoughts, questions, or travel suggestions leave me a note on Twitter @kelleyrobinson.