Cascade Bicycle Club Add to Google Calendar

Adds a Google Calendar button to Cascade Bicycle free group rides.

// ==UserScript==
// @name         Cascade Bicycle Club Add to Google Calendar
// @namespace    http://sunnyrodriguez.com
// @version      0.1
// @description  Adds a Google Calendar button to Cascade Bicycle free group rides.
// @author       splttingatms
// @match        https://www.cascade.org/node/*
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    var ride = parseRidePage();
    var calendarEvent = toCalendarEvent(ride);
    var button = createGoogleCalendarButton(calendarEvent);
    getFirstElementByClassName(document, "sharethis-wrapper").appendChild(button);

    function buildUrl(url, parameters){
        var keyValues = [];
        for(var key in parameters) {
            if (parameters.hasOwnProperty(key)) {
                var value = parameters[key];
                keyValues.push(encodeURIComponent(key) + "=" + encodeURIComponent(value));
            }
        }

        if (keyValues.length > 0){
            url = url + "?" + keyValues.join("&");
        }

        return url;
    }

    function parseTime(timeString) {
        if (timeString === '') return null;

        var time = timeString.match(/(\d+)(:(\d\d))?\s*(p?)/i);
        if (time === null) return null;

        var hours = parseInt(time[1],10);
        if (hours == 12 && !time[4]) {
            hours = 0;
        }
        else {
            hours += (hours < 12 && time[4])? 12 : 0;
        }
        var d = new Date();
        d.setHours(hours);
        d.setMinutes(parseInt(time[3],10) || 0);
        d.setSeconds(0, 0);
        return d;
    }

    function toYYYYMMDDHHMMSSFormat(date) {
        function pad2(n) {  // always returns a string
            return (n < 10 ? '0' : '') + n;
        }

        return date.getFullYear() +
            pad2(date.getMonth() + 1) +
            pad2(date.getDate()) + "T" +
            pad2(date.getHours()) +
            pad2(date.getMinutes()) +
            pad2(date.getSeconds());
    }

    function getFirstElementByClassName(root, name) {
        var elements = root.getElementsByClassName(name);
        if (elements.length < 1)
            throw "an element with class name \"" + name + "\" does not exist";
        return elements[0];
    }

    function getFieldElement(fieldName) {
        try {
            var field = getFirstElementByClassName(document, fieldName);
            return getFirstElementByClassName(field, "field-items");
        } catch (err) {
            throw "a field with name \"" + fieldName + "\" does not exist";
        }
    }

    function getFieldElementOrDefault(fieldName) {
        try {
            return getFieldElement(fieldName);
        } catch (err) {
            console.warn(err);
            return null;
        }
    }

    function getFieldValue(fieldName) {
        return getFieldElement(fieldName).innerText.trim().replace(/\r\n|\r|\n/g, " ").trim();
    }

    function getFieldValueOrDefault(fieldName) {
        try {
            return getFieldValue(fieldName);
        } catch (err) {
            console.warn(err);
            return null;
        }
    }

    function toCalendarEvent(ride) {
        var description = "Ride URL: " + ride.url;
        if (ride.distanceText !== null) description += "\nDistance: " + ride.distanceText;
        if (ride.elevationGain !== null) description += "\nElevation Gain: " + ride.elevationGain;
        if (ride.paces !== null) description += "\nPace(s): " + ride.paces;
        if (ride.terrain !== null) description += "\nTerrain: " + ride.terrain;
        if (ride.regroup !== null) description += "\nRegroup: " + ride.regroup;
        if (ride.weatherCancels !== null) description += "\nWeather Cancels? " + ride.weatherCancels;
        if (ride.links !== null) description += "\nLinks: " + ride.links;
        description += "\nDescription:\n\n" + ride.description;

        return {
            title: "[" + ride.distanceValue + "mi] " + ride.title,
            dateRange: toYYYYMMDDHHMMSSFormat(ride.startDateTime) + "/" + toYYYYMMDDHHMMSSFormat(ride.endDateTime),
            location: ride.location,
            description: description
        };
    }

    function getRideDateTimeText() {
        try {
            return getFieldValue("field-name-field-global-datetime");
        } catch (err) {
            return getFirstElementByClassName(document, "date-display-single");
        }
    }

    function getRideLinksAsCommaSeparatedList() {
        try {
            var links = [];
            var anchors = getFieldElement("field-name-field-daily-links").getElementsByTagName("a");
            for (var i = 0; i < anchors.length; i++)
                links.push(anchors[i].href);
            return links.join(", ");
        } catch (err) {
            return null;
        }
    }

    function getRideStartDateTime() {
        var dateTime = getRideDateTimeText(); // format "Friday, May 20, 2016 - 10:00am"
        var date = new Date(dateTime.split("-")[0].trim());
        var time = parseTime(dateTime.split("-")[1].trim());
        var startDateTime = new Date(date.setHours(time.getHours()));
        return startDateTime;
    }

    function addHours(dateTime, n) {
        return new Date(new Date(dateTime).setHours(dateTime.getHours() + 3));
    }

    function parseRidePage() {
        try {
            var rideLink = window.location.href;
            var name = getFirstElementByClassName(document, "page-title").innerText.trim();
            var location = getFieldValue("field-name-field-daily-locations").replace("Directions", "").trim();
            var distanceText = getFieldValue("field-name-field-daily-distance");
            var distanceValue = Number(distanceText.split(" ")[0]); // text is a string with format "XX.XX miles"
            var rideDescription = getFieldValue("field-type-text-with-summary");
            var startDateTime = getRideStartDateTime();
            var endDateTime = addHours(startDateTime, 3); // assume rides take 3hrs

            // non-critical ride properties
            var contactInformation = getFieldValueOrDefault("field-name-field-global-contacts");
            var elevationGain = getFieldValueOrDefault("field-name-field-daily-elevation");
            var paces = getFieldValueOrDefault("field-name-field-daily-pace");
            var terrain = getFieldValueOrDefault("field-name-field-daily-terrain");
            var regroup = getFieldValueOrDefault("field-name-field-daily-regroup");
            var weatherCancels = getFieldValueOrDefault("field-name-field-daily-weather");
            var links = getRideLinksAsCommaSeparatedList();

            return {
                url: rideLink,
                title: name,
                startDateTime: startDateTime,
                endDateTime: endDateTime,
                location: location,
                contactInformation: contactInformation,
                distanceText: distanceText,
                distanceValue: distanceValue,
                elevationGain: elevationGain,
                paces: paces,
                terrain: terrain,
                regroup: regroup,
                weatherCancels: weatherCancels,
                description: rideDescription,
                links: links,
            };
        } catch (err) {
            throw "could not parse page: " + err;
        }
    }

    function toGoogleCalendarAddUrl(event) {
        return buildUrl("http://www.google.com/calendar/event", {
            action: "TEMPLATE",
            dates: calendarEvent.dateRange,
            text: calendarEvent.title,
            details: calendarEvent.description,
            location: calendarEvent.location,
            trp: false});
    }

    function createGoogleCalendarButton(event) {
        var image = document.createElement("img");
        image.src = "//www.google.com/calendar/images/ext/gc_button1.gif";

        var link = document.createElement("a");
        link.href = toGoogleCalendarAddUrl(calendarEvent);
        link.target = "_blank"; // open link in new window
        link.style = "text-decoration:none; border:none;";
        link.appendChild(image);

        var paragraph = document.createElement("p");
        paragraph.appendChild(link);

        return paragraph;
    }
})();