-
-
Save tguar/94c8b1e05191bd3f25936e303286de28 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# -*- 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