Skip to content

Instantly share code, notes, and snippets.

@tguar

tguar/myskill.py Secret

Last active July 31, 2016 01:50
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tguar/94c8b1e05191bd3f25936e303286de28 to your computer and use it in GitHub Desktop.
Save tguar/94c8b1e05191bd3f25936e303286de28 to your computer and use it in GitHub Desktop.
# -*- coding: utf-8 -*-
import logging
import re
from six.moves.urllib.request import urlopen
import unirest
import json
from datetime import datetime
import arrow
import string
from flask import Flask
from flask_ask import Ask, request, session, question, statement
# Disclaimer: By no means can I claim this work is original. A
# very special thanks to John Wheeler (https://github.com/johnwheeler),
# creator of flask_ask. The following code is a mdoerately modified
# example from his samples folder.
# Also, please note: you will need your own Mashape key for Transloc
# Finally, this is my first time using Python so I imagine there are more
# Python-y ways to solve some of the problems below. That's an exercise for
# the reader.
app = Flask(__name__)
ask = Ask(app, "/")
logging.getLogger('flask_ask').setLevel(logging.DEBUG)
# RTS Station Codes = Transloc Codes
# Displaying one for brevity
STATION_CODE_100 = "4090618"
# RTS Station Names = RTS Station Codes
# Displaying one for brevity
STATIONS = {}
STATIONS["Gtec"] = STATION_CODE_100
# Transloc Routes = RTS Route
# Displaying one for brevity
ROUTES = {}
ROUTES["4001150"] = "1"
# Constant defining number of events to be read at one time.
PAGINATION_SIZE = 1
# Length of the delimiter between individual events.
DELIMITER_SIZE = 2
# Size of events
SIZE_OF_EVENTS = 10
# Constant defining session attribute key for the event index
SESSION_INDEX = 'index'
# Constant defining session attribute key for the event text key for date of events.
SESSION_TEXT = 'text'
SESSION_STOP = "stop"
STOP_NAME = "stop"
ROUTE_NAME = None
@ask.launch
def launch():
speech_output = "Bus Bus. What's your bus stop?"
reprompt_text = "With Bus Bus you can find the next buses near your stop. " + \
"For example, you could say Bus Bus get the next 15 at Rosa Parks Downtown Station. " + \
"Or, you could say Bus Bus get the next bus at Butler Plaze. " + \
"Now, what's your bus stop?"
return question(speech_output).reprompt(reprompt_text)
# Gets station and route from user
# Returns audio/visual output of one event
@ask.intent('GetFirstBusIntent')
def get_first_bus(Stations, Routes):
# Declare global variables
global STOP_NAME
global ROUTE_NAME
# Set variables
ROUTE_NAME = Routes
STOP_NAME = Stations
# Need to get the routes from Transloc
events = _get_json_events_from_transloc(STOP_NAME, ROUTE_NAME)
# If no events, tell the user
if not events:
speech_output = "There are no buses that satisfy your query. Please try again later."
return statement('<speak>{}</speak>'.format(speech_output))
# If there are events return them
else:
card_title = ""
speech_output = ""
card_output = ""
card_title = "RTS Bus "
speech_output = "The "
card_output = ""
# Return the next bus (since PAGINATION_SIZE == 1)
for i in range(PAGINATION_SIZE):
speech_output += "{}".format(events[i][1]) + " arrives " + "{}".format(events[i][0]) + ". "
card_output += "The next bus at " + STOP_NAME + " is the {}".format(events[i][1]) + ". It arrives in " + "{}".format(events[i][0]) + ". "
# If no route was specified, just ask for next bus at stop
if ROUTE_NAME == None:
speech_output += "Would you like to know the next bus for " + STOP_NAME + "?"
# If there was a bus route specified, ask for more of the same route at the stop
else:
speech_output += "Would you like to know the next " + ROUTE_NAME + " at " + STOP_NAME + "?"
card_output += "Would you like to know the next bus?"
reprompt_text = "Should I repeat that?"
session.attributes[SESSION_INDEX] = PAGINATION_SIZE
session.attributes[SESSION_TEXT] = events
speech_output = '<speak>{}</speak>'.format(speech_output)
return question(speech_output).reprompt(reprompt_text).simple_card(card_title, card_output)
# Gets: None
# Returns audio/visual output for the rest of the events
@ask.intent('GetNextBusIntent')
def get_next_bus():
events = session.attributes[SESSION_TEXT]
index = session.attributes[SESSION_INDEX]
card_title = "RTS Buses"
speech_output = ""
card_output = ""
i = 0
# If there is only one bus, it would have been read in get_first_bus so there
# are no more buses to give to user. Alternatively, if the incremented index is
# equal to the length of events that means there are no more buses to return.
# Therefore, we should alert the user and close the skill.
if len(events) == 1 or i == len(events) or index == len(events):
speech_output = "I'm sorry, but no more " + "{}".format(events[0][1]) + " arrive in the next hour. "
card_output = "No more " + "{}".format(events[0][1]) + " arrive in the next hour. "
return stop(speech_output)
else:
# If there are more buses to report then continue to inform the user of the remaining
# buses available to the stop. Increment i for the check.
while i < PAGINATION_SIZE and index < len(events):
speech_output += "The " + "{}".format(events[index][1]) + " arrives " + "{}".format(events[index][0]) + ". "
card_output += "The next bus is the " + "{}".format(events[index][1]) + ". It arrives " + "{}".format(events[index][0]) + ". "
i += 1
index += 1
speech_output += "Would you like to know the next bus?"
# Reprompt if necessary
reprompt_text = "Did you want to know the next bus for " + STOP_NAME + "? "
session.attributes[SESSION_INDEX] = index
speech_output = '<speak>{}</speak>'.format(speech_output)
return question(speech_output).reprompt(reprompt_text).simple_card(card_title, card_output)
@ask.intent('AMAZON.StopIntent')
def stop(text = ""):
return statement(text + "Goodbye")
@ask.intent('AMAZON.CancelIntent')
def cancel():
return statement("Goodbye")
@ask.session_ended
def session_ended():
return "", 200
# Gets stop_name and route_name
# Returns mashape response and route_number
def _get_json_events_from_transloc(stop_name, route_name):
# Set to None in case user did not specify
route_number = None
# Format Alexa speech to Capitalize The First Letter Of Each Word
# This will match station name string format
station = STATIONS.get(string.capwords(stop_name))
# Find key in ROUTES based on value. In this case,
# find route_number in ROUTES that's equal to ROUTE_NAME
# Reminder, ROUTE_NAME is a global variable that holds the
# user's route specification. Example, the 35 [bus].
route = route_name
for route in ROUTES:
if ROUTES[route] == ROUTE_NAME:
route_number = route
# Call Mashape's hosted Transloc API
# URL changes based on user's specified bus stops
# returns json formatted data
response = unirest.get("https://transloc-api-1-2.p.mashape.com/arrival-estimates.json?agencies=116&callback=call&stops=%s" % station,
headers={
"X-Mashape-Key": "your_mashape_key",
"Accept": "application/json"
}
)
# .body formats raw response
unirest_response = response.body
# If route is empty there are no routes available
# Go back to get_first_bus to return an empty event
# Else parse response
if route == None:
return None
else:
return _parse_json(unirest_response, route_number)
# Gets json response from mashape and bus route id
# Returns list of events containing arrival_times, bus_route_ids, and station_id
def _parse_json(unirest_response, route_id):
# If data is empty there are no routes available
# Go back to get_first_bus to return an empty event
if unirest_response['data'] == []:
return None
else:
number_of_arrivals = len(unirest_response['data'][0]['arrivals'])
route_number = route_id
# If route is None that means the user just wants all buses for that
# route returned.
if route_id == None:
# If data is empty there are no routes available
# Go back to get_first_bus to return an empty event
if number_of_arrivals == 0:
return
# Else, for each arriving bus create an event with arrival time,
# bus id, and station id
events = [None]*number_of_arrivals
i = 0
while (i < number_of_arrivals):
arrival_times = unirest_response['data'][0]['arrivals'][i]['arrival_at']
bus_route_ids = unirest_response['data'][0]['arrivals'][i]['route_id']
station_id = unirest_response['data'][0]['stop_id']
events[i] = [arrival_times, bus_route_ids, station_id]
i = i + 1
# Since the user specified a route we need to parse through the
# unirest_response and only grab data relevant to that route.
# Then, for each arriving bus that is on the route specified,
# create an event with arrival time, bus id, and station id
else:
number_of_arrivals = len(unirest_response['data'][0]['arrivals'])
events = [None]*number_of_arrivals
i = 0
while i < number_of_arrivals:
# The difference between this loop and the one above is the
# check against route_number on the line below.
if unirest_response['data'][0]['arrivals'][i]['route_id'] == route_number:
arrival_times = unirest_response['data'][0]['arrivals'][i]['arrival_at']
bus_route_ids = unirest_response['data'][0]['arrivals'][i]['route_id']
station_id = unirest_response['data'][0]['stop_id']
events[i] = [arrival_times, bus_route_ids, station_id]
i = i + 1
# If a route returned in unirest_response does not match
# the specified route then i is incremented. Empty events
# are removed in _humanize: L = filter(None, var)
else:
i = i + 1
return _humanize(events)
# Gets list of events: arrival_times, bus_route_ids, station_id.
# Returns list of events: humanized_time, humanized_route.
def _humanize(events):
# Empty events are filtered out
filtered_events = filter(None, events)
number_of_arrivals = len(filtered_events)
humanized_events = [None]*number_of_arrivals
humanized_time = [None]*number_of_arrivals
i = 0
while (i < number_of_arrivals):
# Humanize time
arrowObj = arrow.get(filtered_events[i][0])
tmpDatetime = arrowObj.datetime
local = arrowObj.to('US/Eastern')
humanized_time = local.humanize()
# Humanize route
humanized_route = ROUTES.get(filtered_events[i][1])
# Add humanized_time and humanized_route to humanized_events
humanized_events[i] = [humanized_time, humanized_route]
i = i + 1
return humanized_events
if __name__ == '__main__':
app.run(debug=True)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment