<repo_name>tingxia/brownHome<file_sep>/routes/events.js
/**
 * Created by Bev on 4/23/17.
 */
var unirest = require('unirest');
var parseString = require('xml2js').parseString;

const TIME_PERIOD_PARAMETER = 'TimePeriod'; // used in BrownEvents intent
const EVENT_CATEGORY_ENTITY = 'EventCategory';

module.exports = {
    handleBrownEvents: function (assistant) {
        var days = calcDays(assistant.getArgument(TIME_PERIOD_PARAMETER));
        
        var eventCategory = assistant.getArgument(EVENT_CATEGORY_ENTITY);
        if (eventCategory == null) {
            eventCategory = "";
        }
        var message = eventCategory + " events in the next " + days + " days are: ";
        unirest.get("http://events.brown.edu:/cal/main/listEvents.do?b=de&skinName=rss-list&setappvar=summaryMode(details)&days=" + days + "&cat=" + eventCategory)
            .header("Accept", "application/xml")
            .end(function (result) {
                parseString(result.body, function (err, result) {
                    var list_events = result.rss.channel[0].item;
                    if (list_events === undefined) {
                        assistant.ask("There are no " + eventCategory + " events in the next " + days + "days");
                        return;
                    }
                    for (var i = 0; i < list_events.length; i++) {
                        message = message + " " + list_events[i].title;
                    }
                    if (list_events.length == 0) {
                        assistant.ask("There are no " + eventCategory + " events in the next " + days + "days");
                    } else {
                        assistant.ask(message);
                    }
                });
            });
    }
};

//  input duration: {"amount": 10, "unit": min} or string: today, week, month
function calcDays(duration) {
    if (duration == null) {
        return 2; // by default
    }

    if (duration == "day" || duration == "today") {
        return 1;
    } else if (duration == "week"){
        return 7;
    } else if (duration == "month") {
        return 31;
    }

    switch (duration["unit"]) {
        case "day":
            return duration.amount;
        case "wk":
            return duration.amount * 7;
        case "month":
            return duration.amount * 31;
    }

    return 2; // by default returns 2 days
}<file_sep>/README.md
# brownHome
### Backend Service Setup:
1. run "npm install" to get dependencies. No need to track them in git.
2. run "node loader.js" to install and populate database. The database will be under /data.

### To run Backend Server:
1. ngrok:  Install ngrok to mask localhost to an https or http url for testing purpose with api_ai.
  * In the directory where you have downloaded ngrok, run ./ngrok http 3000. You will see the http or https url your localhost:3000 maps to. Copy either over (https when previewing), then opening your api_ai fulfillment tab on the left of your api_ai interface, paste the http/https url to the URL field and save.
2. Run the server by typing "node bin/www" in the brownHome directory. It is served at localhost:3000.  Now your api_ai should be able to talk to your localhost server.

### Frontend Api.Ai Setup to interact with Backend server:
1. Navigate to [api.ai](https://api.ai/) and log in to your account.
2. Create a new agent.  Name it BrownHome.
3. Navigate to the settings of the agent. (Click on the cog-icon to the right of the agent name on the left-handside menu.)
4. Choose the "Export and Import" tab.  
5. Choose "Restore from Zip" and restore the newest version of the api_ai zip files found in api_ai.
6. Enable the Webhook in Fulfillment (Click on Fulfillment on the left-handside menu).  For the URL, put the URL for ngrok in step #1 for setting up the Backend server (as before, https for preview).  You may need to manually check off "Use webhook" under "Fulfillment" for each intent imported.

### After setting up Backend Server & Api.AI, test the Action on Google Home:
1. Navigate to "Integrations" on the left-handside menu. 
2. Enable Actions on Google, setting "Invocation name for testing" to "Brown Home".
3. Authorize and then Preview the Action using [Google's Web Simulator](https://developers.google.com/actions/tools/web-simulator).  If your Google Home is connected with the same account the Action is authorized with, you can test the Action on your Google Home by invoking the Action by saying "Talk to Brown Home."  Note that previews only last for 24 hours.
  * For more information on the web simulator: https://developers.google.com/actions/tools/testing  
<file_sep>/routes/index.js
// entity documentation: https://docs.api.ai/docs/concept-entities#section-developer-enum-type-entities

var express = require('express');

var router = express.Router();
var unirest = require('unirest');
const ApiAiAssistant = require('actions-on-google').ApiAiAssistant;
var laundryFunction = require('./laundry.js');
var shuttleFunction = require('./shuttle.js');
var eventsFunction = require('./events.js');
var diningFunctions = require('./dining.js');

// ACTIONS
const SHUTTLE = 'Shuttle';
const DINING_MENU = "Dining";
const BROWN_EVENTS = 'BrownEvents';
const LAUNDRY = "Laundry";
const DINING_HOURS = "dining-hours";

/* GET home page. */
router.get('/', function(req, res, next) {
  res.render('index', { title: 'Express' });
});

router.get('/brownCentric', function(req, res, next) {
  res.render('index', { title: 'Express brown centric' });
});

/* GET home page. */
router.post('/', function(req, res, next) {
  const assistant = new ApiAiAssistant({request: req, response: res});

  const actionMap = new Map();
  actionMap.set(SHUTTLE, shuttleFunction.handleShuttle);
  actionMap.set(DINING_MENU, diningFunctions.handleDining);
  actionMap.set(DINING_HOURS, diningFunctions.handleDiningHours);
  actionMap.set(BROWN_EVENTS, eventsFunction.handleBrownEvents);
  actionMap.set(LAUNDRY, laundryFunction.handleLaundry);
  assistant.handleRequest(actionMap);
});

module.exports = router;
<file_sep>/routes/util.js
module.exports = {
    /*
        input: 
            int_hours: integer army time hours
            int_minutes: # minutes past the hour
        returns:
            string formatted:
                "hours:minutes am" or HH:MM AM
                where the first H is optional, but always has two digits for minutes
    */
    convertTimeToSpeakableString: function (int_hours, int_minutes) {
        var str_hours = "";
        var str_am_pm = "";
        var str_minutes = int_minutes + "";

        if (int_hours == 0) {
            str_hours = "12";
            str_am_pm = "am";
        } else if (int_hours > 12) {
            str_hours = (int_hours - 12) + "";
            str_am_pm = "pm";
        } else {
            str_hours = int_hours + "";
            str_am_pm = "am";
        }

        if (int_minutes < 10) {
            str_minutes = "0" + int_minutes;
        }

        return str_hours + ":" + str_minutes + " " + str_am_pm;
    }
}<file_sep>/loader.js
/**
 * Created by Ting on 4/15/17.
 */
//// loader stuff
var anyDB = require('any-db');
var unirest = require('unirest');
var conn = anyDB.createConnection('sqlite3://data/test.db');
//create table messages
conn.query("DROP table IF EXISTS shuttleStops");
var sqlCreate = "CREATE TABLE shuttleStops (" +
    "id INTEGER PRIMARY KEY AUTOINCREMENT, " +
    "code TEXT, " +
    "lat NUMBER, " +
    "lng number, " +
    "route_id TEXT, " +
    "stop_id TEXT, " +
    "name TEXT);";
conn.query(sqlCreate, function () {
    console.dir("Made table shuttleStops!");
});

unirest.get("https://transloc-api-1-2.p.mashape.com/stops.json?agencies=635&callback=call")
    .header("X-Mashape-Key", "<KEY>")
    .header("Accept", "application/json")
    .end(function (result) {
        console.log(result.body.data[0]);
        for (i=0; i < result.body.data.length; i++) {
            var code = result.body.data[i].code,
                lat = result.body.data[i].location.lat,
                lng =result.body.data[i].location.lng,
                route_id = result.body.data[i].routes[0],
                stop_id =result.body.data[i].stop_id,
                name = result.body.data[i].name ;
            var row = [code, lat, lng, route_id,stop_id, name.toLowerCase()];
            conn.query('INSERT INTO shuttleStops VALUES (NULL, $1, $2, $3, $4, $5, $6)', row, function (err) {
                if (err) {
                    console.log('Failed to insert row in the database.');
                    console.error(err);
                } else {
                    console.log("New stop info has been inserted into the database");
                }
            });
        }
    });