<commit_before>'use strict';

var _ = require('lodash');
var Intake = require('intake');
var intake = new Intake();

const fs = require('fs');

const regExpBracesAndOne = /\[[1]\]/;
const regExpBracesAndTwo = /\[[2]\]/;
const regExpBracesAndFour = /\[[4]\]/;
const regExpCapitalLetterThenPeriod = /[A-Z]\./;

module.exports = {
    /**
        * primaryStats2json
        * Expects data to be sent that is read from a Strat-O-Matic
        * print file generated from the team primary stats interface
        * @param primaryStatsPRT {string}
        * @return {string}
    */
    primaryStats2json: function(primaryStatsPRT) {
        var jsonStats = {
            errors: []
        };
        if (intake.isEmptyOrUndefined(primaryStatsPRT)) {
            var error = {
                message: 'The primary stats data is empty or undefined'
            }
            jsonStats.errors.push(error);
        }
        else {
            var lines = primaryStatsPRT.split(/\r?\n/);
            jsonStats.teams = [];
            var teamLineFound = false;
            var primaryStatsYearFound = false;
            var battingStatsHeaderLineParsed = false;
            var battingColumns = [];
            var pitchingStatsHeaderLineParsed = false;
            var pitchingColumns = [];
            var teamIndex = -1;
            var statsMode = '';    // switch between batting and pitching stats mode
            const primaryPlayerStatistics = 'Primary Player Statistics For';
            var statsHeaderLine = '';
            _.forEach(lines, function (line) {
                // find each team based on "Primary Player Statistics"
                if (line.indexOf(primaryPlayerStatistics) > 0) {
                    teamLineFound = true;
                    var teamLine = line.substr(primaryPlayerStatistics.length + 9);
                    if (primaryStatsYearFound === false) {
                        jsonStats.year = line.substr(primaryPlayerStatistics.length + 4, 4);
                        primaryStatsYearFound = true;
                    }
                    var totalsAfterGames = ' Totals After';
                    var team = teamLine.substr(0, teamLine.indexOf(totalsAfterGames));
                    jsonStats.teams.push({"team": team});
                    teamIndex++;
                    jsonStats.teams[teamIndex].batters = [];
                    jsonStats.teams[teamIndex].pitchers = [];
                }
                if (teamLineFound === false) {
                    // determine if the line is empty, a stats header row, or player row
                    if (line !== '') {
                        statsHeaderLine = ((line.search(regExpBracesAndOne) === 0) && line.indexOf('NAME') > 0);
                        if ((statsHeaderLine !== false) && (line.indexOf('BAVG') > 0)) {
                            statsMode = 'B';
                        }
                        if ((statsHeaderLine !== false) && (line.indexOf('ERA') > 0)) {
                            statsMode = 'P';
                        }
                        if ((statsMode === 'B') && (battingStatsHeaderLineParsed === false)) {
                            // parse stats header line once
                            line = line.replace(regExpBracesAndOne, '');
                            var battingHeaderLineColumnHeadings = line.split(/(\s+)/);
                            _.forEach(battingHeaderLineColumnHeadings, function (columnHeading) {
                                if (columnHeading.trim() !== '') {
                                    battingColumns.push(columnHeading);
                                }
                            });
                            battingStatsHeaderLineParsed = true;
                        }
                        if ((statsMode === 'P') && (pitchingStatsHeaderLineParsed === false)) {
                            // parse stats header line once
                            line = line.replace(regExpBracesAndOne, '');
                            var pitchingHeaderLineColumnHeadings = line.split(/(\s+)/);
                            _.forEach(pitchingHeaderLineColumnHeadings, function (columnHeading) {
                                if (columnHeading.trim() !== '') {
                                    pitchingColumns.push(columnHeading);
                                }
                            });
                            pitchingStatsHeaderLineParsed = true;
                        }
                        if (statsHeaderLine === false) {
                            // if the line is not [2]
                            if (line.search(regExpBracesAndTwo) === -1) {
                                // then it's a stats line
                                if (line.search(regExpCapitalLetterThenPeriod) === 0) {
                                    // split on [4]
                                    //  left side is player name, right side are stats
                                    var playerLine = line.split(regExpBracesAndFour);
                                    // then the line contains a capital letter and a period
                                    var statsColumns = playerLine[1].split(/(\s+)/);
                                    if (statsMode === 'B') {
                                        var battingStats = {};
                                        var battingStatsIndex = 0;
                                        // set first index value to the player name
                                        battingStats[battingColumns[battingStatsIndex]] = playerLine[0];
                                        battingStatsIndex++;
                                        _.forEach(statsColumns, function (statColumn) {
                                            if (statColumn.trim() !== '') {
                                                statColumn = statColumn.replace(regExpBracesAndFour, '');
                                                battingStats[battingColumns[battingStatsIndex]] = statColumn;
                                                battingStatsIndex++;
                                            }
                                        });
                                        jsonStats.teams[teamIndex].batters.push(battingStats);
                                    }
                                    if (statsMode === 'P') {
                                        var pitchingStats = {};
                                        var pitchingStatsIndex = 0;
                                        // set first index value to the player name
                                        pitchingStats[pitchingColumns[pitchingStatsIndex]] = playerLine[0];
                                        pitchingStatsIndex++;
                                        _.forEach(statsColumns, function (statColumn) {
                                            if (statColumn.trim() !== '') {
                                                statColumn = statColumn.replace(regExpBracesAndFour, '');
                                                pitchingStats[pitchingColumns[pitchingStatsIndex]] = statColumn;
                                                pitchingStatsIndex++;
                                            }
                                        });
                                        jsonStats.teams[teamIndex].pitchers.push(pitchingStats);
                                    }
                                }
                            }
                        }
                    }
                }
                teamLineFound = false;
            });
            // if no teams were found then the file contents are not primary stats
            if (jsonStats.teams.length === 0) {
                var error = {
                    message: 'File contents do not match expected Strat-O-Matic primary stats'
                }
                jsonStats.errors.push(error);
                delete jsonStats.teams;
            }
        }
        return jsonStats;
    },
    /**
     * leagueStandings2json
     * Expects data to be sent that is read from a Strat-O-Matic
     * print file generated from the league standings interface
     * @param leagueStandingsPRT {string}
     * @return {string}
     */
    leagueStandings2json: function(leagueStandingsPRT) {
        var leagueStandingsYearFound = false;
        var jsonStandings = {
            errors: []
        };
        if (intake.isEmptyOrUndefined(leagueStandingsPRT)) {
            var error = {
                message: 'The league standings data is empty or undefined'
            }
            jsonStandings.errors.push(error);
        }
        else {
            jsonStandings.conferences = [];
            var lines = leagueStandingsPRT.split(/\r?\n/);
            const leagueStandingsFor = 'LEAGUE STANDINGS FOR';
            var leagueStandingsForLine = '';
            var standingsHeaderLine = '';
            var overallRecordLine = '';
            var overallRecordLineFound = false;
            var wildCardLine = '';
            var wildCardLineFound = false;
            _.forEach(lines, function (line) {
                if (line.indexOf(leagueStandingsFor) > 0) {
                    leagueStandingsYearFound = true;
                    jsonStandings.year = line.substr(leagueStandingsFor.length + 4, 4);
                }
                if (leagueStandingsYearFound === true) {
                    // determine if the line is empty, a stats header row, or player row
                    if (line !== '') {
                        standingsHeaderLine = ((line.search(regExpBracesAndOne) === 0) && (line.indexOf('WON') > 0) && (line.indexOf('LOST') > 0) && (line.indexOf('PCT') > 0) && (line.indexOf('GB') > 0) && (line.indexOf('MAGIC#') > 0) && (line.indexOf('Standings') === -1));
                        if (standingsHeaderLine !== false) {
                            // this line is the conference abbreviation, division, and standings header columns
                            //console.log(line);
                            var standingsColumns = line.split(/(\s+)/);
                            // parse conferences
                            var lineConference = standingsColumns[0].replace(regExpBracesAndOne, '').trim();
                            var conferenceFound = _.findIndex(jsonStandings.conferences, function(c) {
                                return c.conference === lineConference;
                            });
                            if (conferenceFound < 0) {
                                var conferenceObj = {
                                    conference: lineConference,
                                    divisions: []
                                };
                                jsonStandings.conferences.push(conferenceObj);
                            }
                            // parse divisions
                            var lineDivision = standingsColumns[2];
                            // find the index of the conference
                            conferenceFound = _.findIndex(jsonStandings.conferences, function(c) {
                                return c.conference === lineConference;
                            });
                            var divisionFound = _.findIndex(jsonStandings.conferences[conferenceFound].divisions, function(d) {
                                return d.division === lineDivision;
                            });
                            if (divisionFound < 0) {
                                var divisionObj = {
                                    division: lineDivision,
                                    teams: []
                                };
                                jsonStandings.conferences[conferenceFound].divisions.push(divisionObj);
                            }
                        }
                        else {
                            leagueStandingsForLine = ((line.search(regExpBracesAndOne) === 0) && (line.indexOf(leagueStandingsFor) > 0));
                            // ignore "wild card" standings
                            wildCardLine = ((line.search(regExpBracesAndOne) === 0) && (line.indexOf('Wild Card Standings') > 0));
                            if (wildCardLine === true) {
                                wildCardLineFound = true;
                            }
                            // check to see if the line contains OVERALL RECORD
                            overallRecordLine = ((line.search(regExpBracesAndOne) === 0) && (line.indexOf('OVERALL RECORD') > 0));
                            if (overallRecordLine === true) {
                                overallRecordLineFound = true;
                            }
                            if ((leagueStandingsForLine === false) && (overallRecordLineFound === false) && (wildCardLineFound === false)) {
                                // this will be the team and their record
                                //console.log(line);
                            }
                        }
                    }
                }
            });
        }
        //console.log(jsonStandings);
        return jsonStandings;
    },
    readFromFile: function(fileLocation) {
        return fs.readFileSync(fileLocation, 'utf8');
    }
};
<commit_msg>completed standings parsing to JSON<commit_after>'use strict';

var _ = require('lodash');
var Intake = require('intake');
var intake = new Intake();

const fs = require('fs');

const regExpBracesAndOne = /\[[1]\]/;
const regExpBracesAndTwo = /\[[2]\]/;
const regExpBracesAndFour = /\[[4]\]/;
const regExpCapitalLetterThenPeriod = /[A-Z]\./;

module.exports = {
    /**
        * primaryStats2json
        * Expects data to be sent that is read from a Strat-O-Matic
        * print file generated from the team primary stats interface
        * @param primaryStatsPRT {string}
        * @return {string}
    */
    primaryStats2json: function(primaryStatsPRT) {
        var jsonStats = {
            errors: []
        };
        if (intake.isEmptyOrUndefined(primaryStatsPRT)) {
            var error = {
                message: 'The primary stats data is empty or undefined'
            }
            jsonStats.errors.push(error);
        }
        else {
            var lines = primaryStatsPRT.split(/\r?\n/);
            jsonStats.teams = [];
            var teamLineFound = false;
            var primaryStatsYearFound = false;
            var battingStatsHeaderLineParsed = false;
            var battingColumns = [];
            var pitchingStatsHeaderLineParsed = false;
            var pitchingColumns = [];
            var teamIndex = -1;
            var statsMode = '';    // switch between batting and pitching stats mode
            const primaryPlayerStatistics = 'Primary Player Statistics For';
            var statsHeaderLine = '';
            _.forEach(lines, function (line) {
                // find each team based on "Primary Player Statistics"
                if (line.indexOf(primaryPlayerStatistics) > 0) {
                    teamLineFound = true;
                    var teamLine = line.substr(primaryPlayerStatistics.length + 9);
                    if (primaryStatsYearFound === false) {
                        jsonStats.year = line.substr(primaryPlayerStatistics.length + 4, 4);
                        primaryStatsYearFound = true;
                    }
                    var totalsAfterGames = ' Totals After';
                    var team = teamLine.substr(0, teamLine.indexOf(totalsAfterGames));
                    jsonStats.teams.push({"team": team});
                    teamIndex++;
                    jsonStats.teams[teamIndex].batters = [];
                    jsonStats.teams[teamIndex].pitchers = [];
                }
                if (teamLineFound === false) {
                    // determine if the line is empty, a stats header row, or player row
                    if (line !== '') {
                        statsHeaderLine = ((line.search(regExpBracesAndOne) === 0) && line.indexOf('NAME') > 0);
                        if ((statsHeaderLine !== false) && (line.indexOf('BAVG') > 0)) {
                            statsMode = 'B';
                        }
                        if ((statsHeaderLine !== false) && (line.indexOf('ERA') > 0)) {
                            statsMode = 'P';
                        }
                        if ((statsMode === 'B') && (battingStatsHeaderLineParsed === false)) {
                            // parse stats header line once
                            line = line.replace(regExpBracesAndOne, '');
                            var battingHeaderLineColumnHeadings = line.split(/(\s+)/);
                            _.forEach(battingHeaderLineColumnHeadings, function (columnHeading) {
                                if (columnHeading.trim() !== '') {
                                    battingColumns.push(columnHeading);
                                }
                            });
                            battingStatsHeaderLineParsed = true;
                        }
                        if ((statsMode === 'P') && (pitchingStatsHeaderLineParsed === false)) {
                            // parse stats header line once
                            line = line.replace(regExpBracesAndOne, '');
                            var pitchingHeaderLineColumnHeadings = line.split(/(\s+)/);
                            _.forEach(pitchingHeaderLineColumnHeadings, function (columnHeading) {
                                if (columnHeading.trim() !== '') {
                                    pitchingColumns.push(columnHeading);
                                }
                            });
                            pitchingStatsHeaderLineParsed = true;
                        }
                        if (statsHeaderLine === false) {
                            // if the line is not [2]
                            if (line.search(regExpBracesAndTwo) === -1) {
                                // then it's a stats line
                                if (line.search(regExpCapitalLetterThenPeriod) === 0) {
                                    // split on [4]
                                    //  left side is player name, right side are stats
                                    var playerLine = line.split(regExpBracesAndFour);
                                    // then the line contains a capital letter and a period
                                    var statsColumns = playerLine[1].split(/(\s+)/);
                                    if (statsMode === 'B') {
                                        var battingStats = {};
                                        var battingStatsIndex = 0;
                                        // set first index value to the player name
                                        battingStats[battingColumns[battingStatsIndex]] = playerLine[0];
                                        battingStatsIndex++;
                                        _.forEach(statsColumns, function (statColumn) {
                                            if (statColumn.trim() !== '') {
                                                statColumn = statColumn.replace(regExpBracesAndFour, '');
                                                battingStats[battingColumns[battingStatsIndex]] = statColumn;
                                                battingStatsIndex++;
                                            }
                                        });
                                        jsonStats.teams[teamIndex].batters.push(battingStats);
                                    }
                                    if (statsMode === 'P') {
                                        var pitchingStats = {};
                                        var pitchingStatsIndex = 0;
                                        // set first index value to the player name
                                        pitchingStats[pitchingColumns[pitchingStatsIndex]] = playerLine[0];
                                        pitchingStatsIndex++;
                                        _.forEach(statsColumns, function (statColumn) {
                                            if (statColumn.trim() !== '') {
                                                statColumn = statColumn.replace(regExpBracesAndFour, '');
                                                pitchingStats[pitchingColumns[pitchingStatsIndex]] = statColumn;
                                                pitchingStatsIndex++;
                                            }
                                        });
                                        jsonStats.teams[teamIndex].pitchers.push(pitchingStats);
                                    }
                                }
                            }
                        }
                    }
                }
                teamLineFound = false;
            });
            // if no teams were found then the file contents are not primary stats
            if (jsonStats.teams.length === 0) {
                var error = {
                    message: 'File contents do not match expected Strat-O-Matic primary stats'
                }
                jsonStats.errors.push(error);
                delete jsonStats.teams;
            }
        }
        return jsonStats;
    },
    /**
     * leagueStandings2json
     * Expects data to be sent that is read from a Strat-O-Matic
     * print file generated from the league standings interface
     * @param leagueStandingsPRT {string}
     * @return {string}
     */
    leagueStandings2json: function(leagueStandingsPRT) {
        var leagueStandingsYearFound = false;
        var jsonStandings = {
            errors: []
        };
        if (intake.isEmptyOrUndefined(leagueStandingsPRT)) {
            var error = {
                message: 'The league standings data is empty or undefined'
            }
            jsonStandings.errors.push(error);
        }
        else {
            jsonStandings.conferences = [];
            var lines = leagueStandingsPRT.split(/\r?\n/);
            const leagueStandingsFor = 'LEAGUE STANDINGS FOR';
            var leagueStandingsForLine = '';
            var standingsHeaderLine = '';
            var overallRecordLine = '';
            var overallRecordLineFound = false;
            var wildCardLine = '';
            var wildCardLineFound = false;
            var conferenceFound = -1;
            var divisionFound = -1;
            var currentConference = 0;
            var currentDivision = 0;
            _.forEach(lines, function (line) {
                if (line.indexOf(leagueStandingsFor) > 0) {
                    leagueStandingsYearFound = true;
                    jsonStandings.year = line.substr(leagueStandingsFor.length + 4, 4);
                }
                if (leagueStandingsYearFound === true) {
                    // determine if the line is empty, a stats header row, or player row
                    if (line !== '') {
                        standingsHeaderLine = ((line.search(regExpBracesAndOne) === 0) && (line.indexOf('WON') > 0) && (line.indexOf('LOST') > 0) && (line.indexOf('PCT') > 0) && (line.indexOf('GB') > 0) && (line.indexOf('MAGIC#') > 0) && (line.indexOf('Standings') === -1));
                        if (standingsHeaderLine !== false) {
                            // this line is the conference abbreviation, division, and standings header columns
                            //console.log(line);
                            var standingsColumns = line.split(/(\s+)/);
                            // parse conferences
                            var lineConference = standingsColumns[0].replace(regExpBracesAndOne, '').trim();
                            conferenceFound = _.findIndex(jsonStandings.conferences, function(c) {
                                return c.conference === lineConference;
                            });
                            if (conferenceFound < 0) {
                                var conferenceObj = {
                                    conference: lineConference,
                                    divisions: []
                                };
                                jsonStandings.conferences.push(conferenceObj);
                            }
                            // parse divisions
                            var lineDivision = standingsColumns[2];
                            // find the index of the conference
                            currentConference = _.findIndex(jsonStandings.conferences, function(c) {
                                return c.conference === lineConference;
                            });
                            // find the index of the division
                            divisionFound = _.findIndex(jsonStandings.conferences[currentConference].divisions, function(d) {
                                return d.division === lineDivision;
                            });
                            if (divisionFound < 0) {
                                var divisionObj = {
                                    division: lineDivision,
                                    teams: []
                                };
                                jsonStandings.conferences[currentConference].divisions.push(divisionObj);
                            }
                            // find the index of the division
                            currentDivision = _.findIndex(jsonStandings.conferences[currentConference].divisions, function(d) {
                                return d.division === lineDivision;
                            });
                        }
                        else {
                            leagueStandingsForLine = ((line.search(regExpBracesAndOne) === 0) && (line.indexOf(leagueStandingsFor) > 0));
                            // ignore "wild card" standings
                            wildCardLine = ((line.search(regExpBracesAndOne) === 0) && (line.indexOf('Wild Card Standings') > 0));
                            if (wildCardLine === true) {
                                wildCardLineFound = true;
                            }
                            // check to see if the line contains OVERALL RECORD
                            overallRecordLine = ((line.search(regExpBracesAndOne) === 0) && (line.indexOf('OVERALL RECORD') > 0));
                            if (overallRecordLine === true) {
                                overallRecordLineFound = true;
                            }
                            if ((leagueStandingsForLine === false) && (overallRecordLineFound === false) && (wildCardLineFound === false)) {
                                // this will be the team and their record
                                //console.log(line);
                                var teamLineArray = line.split(regExpBracesAndFour);
                                var teamNameArray = teamLineArray[0].split(/(\s+)/);
                                var team = {
                                    teamCity: '',
                                    teamAbbreviation: ''
                                };
                                for (var i = 2; i < teamNameArray.length - 2; i++) {
                                    team.teamCity = team.teamCity += teamNameArray[i];
                                }
                                team.teamAbbreviation = teamNameArray[teamNameArray.length - 1];
                                var teamRecordArray = teamLineArray[1].split(/(\s+)/);
                                team.W = teamRecordArray[2];
                                team.L = teamRecordArray[4];
                                team.PCT = teamRecordArray[6];
                                team.GB = teamRecordArray[8];
                                jsonStandings.conferences[currentConference].divisions[currentDivision].teams.push(team);
                            }
                        }
                    }
                }
            });
        }
        return jsonStandings;
    },
    readFromFile: function(fileLocation) {
        return fs.readFileSync(fileLocation, 'utf8');
    }
};
<|endoftext|>


<commit_before>require('dotenv').config();
console.log(process.env.SLACK_API_TOKEN);

// Required modules
var RtmClient = require('@slack/client').RtmClient;
var RTM_EVENTS = require('@slack/client').RTM_EVENTS;
var WebClient = require('@slack/client').WebClient;
var Client = require('node-rest-client').Client;

// Global variables
var client = new Client({port: process.env.PORT});
var token = process.env.SLACK_API_TOKEN || '';
var apikey = process.env.TICKETMASTER_API_KEY || '';
var rtm = new RtmClient(token);
var web = new WebClient(token);

// Data object for the web.chat.postMessage call
var msgdata = {
	username: "apibot",
	icon_emoji: ":bee:"
};

function sendHelpMenu(message) {
	msgdata.attachments = [{
        "color": "#768692",
        "title": "Count all events in the API",
		"text": "`\\ count`",
		"mrkdwn_in": ["text"]
	}, {
        "color": "#B7C9D3",
        "title": "Count all events sourced by Ticketmaster",
		"text": "`\\ count ticketmaster`",
		"mrkdwn_in": ["text"]
	}, {
        "color": "#768692",
        "title": "Count all events sourced by Universe in Great Britain",
		"text": "`\\ count universe GB`",
		"mrkdwn_in": ["text"]
	}, {
        "color": "#B7C9D3",
        "title": "Search events by keyword",
		"text": "`\\ search event Adele`",
		"mrkdwn_in": ["text"]
	}, {
        "color": "#768692",
        "title": "Get event details by ID",
		"text": "`\\ get event Z7r9jZ1AvOYAP`",
		"mrkdwn_in": ["text"]
	}, {
        "color": "#B7C9D3",
        "title": "Get event details by source ID",
		"text": "`\\ get:source event 19004F8BA9A52268`",
		"mrkdwn_in": ["text"]
	}];
    web.chat.postMessage(message.channel, "Hello! I'm your <http://developer.ticketmaster.com|Ticketmaster API> assistant. Here's a few examples of what I can do:", msgdata);
}

function sendEventCard(message, url, data) {
	if (data.name) {
		var attr, ven, seg, loc = "N/A";
		if (data.classifications.length) {
			seg = data.classifications[0].segment.name;
		}
		if (data._embedded && data._embedded.attractions && data._embedded.attractions.length) {
			attr = "<" + data._embedded.attractions[0].url + "|" + data._embedded.attractions[0].name + ">";
		}
		if (data._embedded && data._embedded.venues && data._embedded.venues.length) {
			ven = "<" + data._embedded.venues[0].url + "|" + data._embedded.venues[0].name + ">";
			loc = data._embedded.venues[0].city.name + ", " + data._embedded.venues[0].state.stateCode + " " + data._embedded.venues[0].postalCode + "\n" + data._embedded.venues[0].country.name;

		}
		msgdata.attachments = [{
            "color": "#768692",
			"title": "<" + data.url + "|" + data.name + "> ",
			"image_url": data.images[0].url,
			"mrkdwn_in": ["text"]
    	}, {
			"color": "#768692",
			"fields": [{
				"title": "Event ID",
				"value": data.id,
				"short": true
			}, {
				"title": "Source ID",
				"value": data.source.id,
				"short": true
			}, {
				"title": "Source",
				"value": data.source.name,
				"short": true
			}, {
				"title": "Attraction",
				"value": attr,
				"short": true
			}, {
				"title": "Venues",
				"value": ven,
				"short": true
			}, {
				"title": "Segment",
				"value": seg,
				"short": true
			}, {
				"title": "Location",
				"value": loc,
				"short": true
			}, {
				"title": "Status",
				"value": (data.active) ? 'Active' : 'Inactive',
				"short": true
			}]
    	}];
		web.chat.postMessage(message.channel, "Here's the event I found: (<"+ url + "|api call>)", msgdata);
	} else {
		web.chat.postMessage(message.channel, "Hmm, something went wrong. #BlameSylvain. Here's the API call I made: " + url, msgdata);
	}
}

function handleRtmMessage(message) { // listening in on the messages in the channels that added me!
	if (message.type == "message" && message.text && (message.text.substring(0, 1) == "\\" || message.text.substring(0, 13) == '<@U1K1RGWUQ>:')) { // Making sure they're asking me to do something!
		console.log('Message:', message);
		msgdata.attachments = [];
		rtm.sendMessage('...', message.channel);
		// Analyze the command
		var arr = message.text.split(' ');
		if (arr[1]) {
			switch(arr[1]) {
			case 'count':
				var url = "https://app.ticketmaster.com/discovery/v2/events.json?apikey="+ apikey +"&size=1";
				if (arr[2]) {
					url += "&source=" + arr[2];
				}
				if (arr[3]) {
					url += "&countryCode=" + arr[3];
				}
				var source = arr[2] || 'The entire marketplace';
				client.get(url, function (data) {
					if (data.page) {
						var msg = source + ' has a total of *' + data.page.totalElements + '* events';
						if (arr[3]) {
							msg += " in " + arr[3];
						}
						web.chat.postMessage(message.channel, msg, msgdata);
					} else {
						web.chat.postMessage(message.channel, "Hmm, something went wrong. #BlameSylvain. Here's the <" + url + "|API call> I made", msgdata);
					}
				});
				break;
			case 'search':
				if (arr[2]) {
					switch(arr[2]) {
					case 'event':
						if (arr[3]) {
							var url = "https://app.ticketmaster.com/discovery/v2/events.json?keyword=" + arr[3] + "&size=1&apikey="+ apikey;
							client.get(url, function (data) {
								sendEventCard(message, url, data._embedded.events[0]);
							});
						} else {
							web.chat.postMessage(message.channel, "I need the event ID", msgdata);
						}
						break;
					case 'attr':
						break;
					case 'venue':
						break;
					default:
						web.chat.postMessage(message.channel, 'Not sure I know what that is :)', msgdata);
						break;
					}
				} else {
					web.chat.postMessage(message.channel, 'What do you want me to get?', msgdata);
				}
				break;
			case 'get':
				if (arr[2]) {
					switch(arr[2]) {
					case 'event':
						if (arr[3]) {
							var url = "https://app.ticketmaster.com/discovery/v2/events/" + arr[3] + ".json?view=internal&apikey="+ apikey;
							client.get(url, function (data) {
								sendEventCard(message, url, data);
							});
						} else {
							web.chat.postMessage(message.channel, "I need the event ID", msgdata);
						}
						break;
					case 'attr':
						break;
					case 'venue':
						break;
					default:
						web.chat.postMessage(message.channel, 'Not sure I know what that is :)', msgdata);
						break;
					}
				} else {
					web.chat.postMessage(message.channel, 'What do you want me to get?', msgdata);
				}
				break;
			case 'get:source':
			case 'legacy':
				if (arr[2]) {
					switch(arr[2]) {
					case 'event':
						if (arr[3]) {
							var url = "https://app.ticketmaster.com/discovery/v2/events/legacy/" + arr[3] + ".json?apikey="+ apikey;
							client.get(url, function (data) {
								sendEventCard(message, url, data);
							});
						} else {
							web.chat.postMessage(message.channel, "I need the event source ID", msgdata);
						}
						break;
					case 'attr':
						break;
					case 'venue':
						break;
					default:
						web.chat.postMessage(message.channel, 'Not sure I know what that is :)', msgdata);
						break;
					}
				} else {
					web.chat.postMessage(message.channel, 'What do you want me to get?', msgdata);
				}
				break;
			case 'help':
				sendHelpMenu(message);
				break;
			}
		} else {
			sendHelpMenu(message);
		}
	}
}

// Start the bot!
rtm.start();
rtm.on(RTM_EVENTS.MESSAGE, handleRtmMessage);
<commit_msg>requiring dotenv only if the variables aren't set<commit_after>if (!process.env.SLACK_API_TOKEN) require('dotenv').config();
console.log(process.env.SLACK_API_TOKEN);

// Required modules
var RtmClient = require('@slack/client').RtmClient;
var RTM_EVENTS = require('@slack/client').RTM_EVENTS;
var WebClient = require('@slack/client').WebClient;
var Client = require('node-rest-client').Client;

// Global variables
var client = new Client({port: process.env.PORT});
var token = process.env.SLACK_API_TOKEN || '';
var apikey = process.env.TICKETMASTER_API_KEY || '';
var rtm = new RtmClient(token);
var web = new WebClient(token);

// Data object for the web.chat.postMessage call
var msgdata = {
	username: "apibot",
	icon_emoji: ":bee:"
};

function sendHelpMenu(message) {
	msgdata.attachments = [{
        "color": "#768692",
        "title": "Count all events in the API",
		"text": "`\\ count`",
		"mrkdwn_in": ["text"]
	}, {
        "color": "#B7C9D3",
        "title": "Count all events sourced by Ticketmaster",
		"text": "`\\ count ticketmaster`",
		"mrkdwn_in": ["text"]
	}, {
        "color": "#768692",
        "title": "Count all events sourced by Universe in Great Britain",
		"text": "`\\ count universe GB`",
		"mrkdwn_in": ["text"]
	}, {
        "color": "#B7C9D3",
        "title": "Search events by keyword",
		"text": "`\\ search event Adele`",
		"mrkdwn_in": ["text"]
	}, {
        "color": "#768692",
        "title": "Get event details by ID",
		"text": "`\\ get event Z7r9jZ1AvOYAP`",
		"mrkdwn_in": ["text"]
	}, {
        "color": "#B7C9D3",
        "title": "Get event details by source ID",
		"text": "`\\ get:source event 19004F8BA9A52268`",
		"mrkdwn_in": ["text"]
	}];
    web.chat.postMessage(message.channel, "Hello! I'm your <http://developer.ticketmaster.com|Ticketmaster API> assistant. Here's a few examples of what I can do:", msgdata);
}

function sendEventCard(message, url, data) {
	if (data.name) {
		var attr, ven, seg, loc = "N/A";
		if (data.classifications.length) {
			seg = data.classifications[0].segment.name;
		}
		if (data._embedded && data._embedded.attractions && data._embedded.attractions.length) {
			attr = "<" + data._embedded.attractions[0].url + "|" + data._embedded.attractions[0].name + ">";
		}
		if (data._embedded && data._embedded.venues && data._embedded.venues.length) {
			ven = "<" + data._embedded.venues[0].url + "|" + data._embedded.venues[0].name + ">";
			loc = data._embedded.venues[0].city.name + ", " + data._embedded.venues[0].state.stateCode + " " + data._embedded.venues[0].postalCode + "\n" + data._embedded.venues[0].country.name;

		}
		msgdata.attachments = [{
            "color": "#768692",
			"title": "<" + data.url + "|" + data.name + "> ",
			"image_url": data.images[0].url,
			"mrkdwn_in": ["text"]
    	}, {
			"color": "#768692",
			"fields": [{
				"title": "Event ID",
				"value": data.id,
				"short": true
			}, {
				"title": "Source ID",
				"value": data.source.id,
				"short": true
			}, {
				"title": "Source",
				"value": data.source.name,
				"short": true
			}, {
				"title": "Attraction",
				"value": attr,
				"short": true
			}, {
				"title": "Venues",
				"value": ven,
				"short": true
			}, {
				"title": "Segment",
				"value": seg,
				"short": true
			}, {
				"title": "Location",
				"value": loc,
				"short": true
			}, {
				"title": "Status",
				"value": (data.active) ? 'Active' : 'Inactive',
				"short": true
			}]
    	}];
		web.chat.postMessage(message.channel, "Here's the event I found: (<"+ url + "|api call>)", msgdata);
	} else {
		web.chat.postMessage(message.channel, "Hmm, something went wrong. #BlameSylvain. Here's the API call I made: " + url, msgdata);
	}
}

function handleRtmMessage(message) { // listening in on the messages in the channels that added me!
	if (message.type == "message" && message.text && (message.text.substring(0, 1) == "\\" || message.text.substring(0, 13) == '<@U1K1RGWUQ>:')) { // Making sure they're asking me to do something!
		console.log('Message:', message);
		msgdata.attachments = [];
		rtm.sendMessage('...', message.channel);
		// Analyze the command
		var arr = message.text.split(' ');
		if (arr[1]) {
			switch(arr[1]) {
			case 'count':
				var url = "https://app.ticketmaster.com/discovery/v2/events.json?apikey="+ apikey +"&size=1";
				if (arr[2]) {
					url += "&source=" + arr[2];
				}
				if (arr[3]) {
					url += "&countryCode=" + arr[3];
				}
				var source = arr[2] || 'The entire marketplace';
				client.get(url, function (data) {
					if (data.page) {
						var msg = source + ' has a total of *' + data.page.totalElements + '* events';
						if (arr[3]) {
							msg += " in " + arr[3];
						}
						web.chat.postMessage(message.channel, msg, msgdata);
					} else {
						web.chat.postMessage(message.channel, "Hmm, something went wrong. #BlameSylvain. Here's the <" + url + "|API call> I made", msgdata);
					}
				});
				break;
			case 'search':
				if (arr[2]) {
					switch(arr[2]) {
					case 'event':
						if (arr[3]) {
							var url = "https://app.ticketmaster.com/discovery/v2/events.json?keyword=" + arr[3] + "&size=1&apikey="+ apikey;
							client.get(url, function (data) {
								sendEventCard(message, url, data._embedded.events[0]);
							});
						} else {
							web.chat.postMessage(message.channel, "I need the event ID", msgdata);
						}
						break;
					case 'attr':
						break;
					case 'venue':
						break;
					default:
						web.chat.postMessage(message.channel, 'Not sure I know what that is :)', msgdata);
						break;
					}
				} else {
					web.chat.postMessage(message.channel, 'What do you want me to get?', msgdata);
				}
				break;
			case 'get':
				if (arr[2]) {
					switch(arr[2]) {
					case 'event':
						if (arr[3]) {
							var url = "https://app.ticketmaster.com/discovery/v2/events/" + arr[3] + ".json?view=internal&apikey="+ apikey;
							client.get(url, function (data) {
								sendEventCard(message, url, data);
							});
						} else {
							web.chat.postMessage(message.channel, "I need the event ID", msgdata);
						}
						break;
					case 'attr':
						break;
					case 'venue':
						break;
					default:
						web.chat.postMessage(message.channel, 'Not sure I know what that is :)', msgdata);
						break;
					}
				} else {
					web.chat.postMessage(message.channel, 'What do you want me to get?', msgdata);
				}
				break;
			case 'get:source':
			case 'legacy':
				if (arr[2]) {
					switch(arr[2]) {
					case 'event':
						if (arr[3]) {
							var url = "https://app.ticketmaster.com/discovery/v2/events/legacy/" + arr[3] + ".json?apikey="+ apikey;
							client.get(url, function (data) {
								sendEventCard(message, url, data);
							});
						} else {
							web.chat.postMessage(message.channel, "I need the event source ID", msgdata);
						}
						break;
					case 'attr':
						break;
					case 'venue':
						break;
					default:
						web.chat.postMessage(message.channel, 'Not sure I know what that is :)', msgdata);
						break;
					}
				} else {
					web.chat.postMessage(message.channel, 'What do you want me to get?', msgdata);
				}
				break;
			case 'help':
				sendHelpMenu(message);
				break;
			}
		} else {
			sendHelpMenu(message);
		}
	}
}

// Start the bot!
rtm.start();
rtm.on(RTM_EVENTS.MESSAGE, handleRtmMessage);
<|endoftext|>


<commit_before>var express = require('express')
var app     = express();

app.set('port', (process.env.PORT || 5000))
app.use(express.static(__dirname + '/public'))

app.get('/', function(request, response) {
    response.send('Hello World!')
});

app.get('/webhook', function(req, res) {
    if (req.query['hub.mode'] === 'subscribe' && req.query['hub.verify_token'] === "QYKLKbipwgVXYTuwXMQxwq7uqz") {
        console.log("Validating webhook");
        res.status(200).send(req.query['hub.challenge']);
    } else {
        console.error("Failed validation. Make sure the validation tokens match.");
        res.sendStatus(403);
    }
});

app.post('/webhook', function(req, res){
    var data = req.body;
    console.log(data);
    // Make sure this is a page subscription
    if(data.object === 'page'){
        // Iterate over each entries - there may be multiple if batched
        data.entry.forEach(function(entry){
            var pageID = entry.id;
            var timeOfEvent = entry.time;

            // Iterate over each messaging event
            entry.messaging.forEach(function(event){
                if(event.message){
                    receivedMessage(event);
                }else{
                    console.log("Webhook received unknown event: ", event);
                }
            });
        });

        res.sendStatus(200);
    }
});

function receivedMessage(event){
    var senderID      = event.sender.id;
    var recipientID   = event.recipient.id;
    var timeOfMessage = event.timestamp;
    var message       = event.message;

    console.log("Received message for user %d and page %d at %d with message:", senderID, recipientID, timeOfMessage);
    console.log(JSON.stringify(message));

    var messageId          = message.mid;
    var messageText        = message.text;
    var messageAttachments = message.attachments;

    if(messageText){
        // If we receive a text message, check to see if it matches a keyword
        // and send back the example. Otherwise, just echo the text we received.
        switch(messageText){
            case 'generic':
                sendGenericMessage(senderID);
            break;

            default:
                sendTextMessage(senderID, messageText);
        }
    }else if(messageAttachments){
        sendTextMessage(senderID, "Message with attachment received");
    }
}

function sendGenericMessage(recipientId, messageText){
    // To be expanded in later sections
}

function sendTextMessage(recipientId, messageText){
    var messageData = {
        recipient: {
            id: recipientId
        },
        message: {
            text: messageText
        }
    };

    callSendAPI(messageData);
}

function callSendAPI(messageData){
    request({
        uri: 'https://graph.facebook.com/v2.6/me/messages',
        qs: { access_token: process.env.PAGE_ACCESS_TOKEN },
        method: 'POST',
        json: messageData

    }, function (error, response, body){
        if(!error && response.statusCode == 200){
            var recipientId = body.recipient_id;
            var messageId   = body.message_id;

            console.log("Successfully sent generic message with id %s to recipient %s", messageId, recipientId);
        }else{
            console.error("Unable to send message.");
            console.error(response);
            console.error(error);
        }
    });
}

app.listen(app.get('port'), function() {
    console.log("Node app is running at localhost:" + app.get('port'))
})
<commit_msg>- Let’s try this again with a sample<commit_after>/*
* Copyright 2016-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the license found in the
* LICENSE file in the root directory of this source tree.
*
*/

/* jshint node: true, devel: true */
'use strict';

const
bodyParser = require('body-parser'),
config     = require('config'),
crypto     = require('crypto'),
express    = require('express'),
https      = require('https'),
request    = require('request');

var app = express();
app.set('port', process.env.PORT || 5000);
app.set('view engine', 'ejs');
app.use(bodyParser.json({ verify: verifyRequestSignature }));
app.use(express.static('public'));

/*
* Be sure to setup your config values before running this code. You can
* set them using environment variables or modifying the config file in /config.
*
*/

// App Secret can be retrieved from the App Dashboard
const APP_SECRET = (process.env.MESSENGER_APP_SECRET) ?
process.env.MESSENGER_APP_SECRET :
config.get('appSecret');

// Arbitrary value used to validate a webhook
const VALIDATION_TOKEN = (process.env.MESSENGER_VALIDATION_TOKEN) ?
(process.env.MESSENGER_VALIDATION_TOKEN) :
config.get('validationToken');

// Generate a page access token for your page from the App Dashboard
const PAGE_ACCESS_TOKEN = (process.env.MESSENGER_PAGE_ACCESS_TOKEN) ?
(process.env.MESSENGER_PAGE_ACCESS_TOKEN) :
config.get('pageAccessToken');

// URL where the app is running (include protocol). Used to point to scripts and
// assets located at this address.
const SERVER_URL = (process.env.SERVER_URL) ?
(process.env.SERVER_URL) :
config.get('serverURL');

if (!(APP_SECRET && VALIDATION_TOKEN && PAGE_ACCESS_TOKEN && SERVER_URL)) {
    console.error("Missing config values");
    process.exit(1);
}

/*
* Use your own validation token. Check that the token used in the Webhook
* setup is the same token used here.
*
*/
app.get('/webhook', function(req, res) {
    if (req.query['hub.mode'] === 'subscribe' &&
    req.query['hub.verify_token'] === VALIDATION_TOKEN) {
        console.log("Validating webhook");
        res.status(200).send(req.query['hub.challenge']);
    } else {
        console.error("Failed validation. Make sure the validation tokens match.");
        res.sendStatus(403);
    }
});

/*
* All callbacks for Messenger are POST-ed. They will be sent to the same
* webhook. Be sure to subscribe your app to your page to receive callbacks
* for your page.
* https://developers.facebook.com/docs/messenger-platform/product-overview/setup#subscribe_app
*
*/
app.post('/webhook', function (req, res) {
    var data = req.body;

    // Make sure this is a page subscription
    if (data.object == 'page') {
        // Iterate over each entry
        // There may be multiple if batched
        data.entry.forEach(function(pageEntry) {
            var pageID = pageEntry.id;
            var timeOfEvent = pageEntry.time;

            // Iterate over each messaging event
            pageEntry.messaging.forEach(function(messagingEvent) {
                if (messagingEvent.optin) {
                    receivedAuthentication(messagingEvent);
                } else if (messagingEvent.message) {
                    receivedMessage(messagingEvent);
                } else if (messagingEvent.delivery) {
                    receivedDeliveryConfirmation(messagingEvent);
                } else if (messagingEvent.postback) {
                    receivedPostback(messagingEvent);
                } else if (messagingEvent.read) {
                    receivedMessageRead(messagingEvent);
                } else if (messagingEvent.account_linking) {
                    receivedAccountLink(messagingEvent);
                } else {
                    console.log("Webhook received unknown messagingEvent: ", messagingEvent);
                }
            });
        });

        // Assume all went well.
        //
        // You must send back a 200, within 20 seconds, to let us know you've
        // successfully received the callback. Otherwise, the request will time out.
        res.sendStatus(200);
    }
});

/*
* This path is used for account linking. The account linking call-to-action
* (sendAccountLinking) is pointed to this URL.
*
*/
app.get('/authorize', function(req, res) {
    var accountLinkingToken = req.query.account_linking_token;
    var redirectURI = req.query.redirect_uri;

    // Authorization Code should be generated per user by the developer. This will
    // be passed to the Account Linking callback.
    var authCode = "1234567890";

    // Redirect users to this URI on successful login
    var redirectURISuccess = redirectURI + "&authorization_code=" + authCode;

    res.render('authorize', {
        accountLinkingToken: accountLinkingToken,
        redirectURI: redirectURI,
        redirectURISuccess: redirectURISuccess
    });
});

/*
* Verify that the callback came from Facebook. Using the App Secret from
* the App Dashboard, we can verify the signature that is sent with each
* callback in the x-hub-signature field, located in the header.
*
* https://developers.facebook.com/docs/graph-api/webhooks#setup
*
*/
function verifyRequestSignature(req, res, buf) {
    var signature = req.headers["x-hub-signature"];

    if (!signature) {
        // For testing, let's log an error. In production, you should throw an
        // error.
        console.error("Couldn't validate the signature.");
    } else {
        var elements = signature.split('=');
        var method = elements[0];
        var signatureHash = elements[1];

        var expectedHash = crypto.createHmac('sha1', APP_SECRET)
        .update(buf)
        .digest('hex');

        if (signatureHash != expectedHash) {
            throw new Error("Couldn't validate the request signature.");
        }
    }
}

/*
* Authorization Event
*
* The value for 'optin.ref' is defined in the entry point. For the "Send to
* Messenger" plugin, it is the 'data-ref' field. Read more at
* https://developers.facebook.com/docs/messenger-platform/webhook-reference/authentication
*
*/
function receivedAuthentication(event) {
    var senderID = event.sender.id;
    var recipientID = event.recipient.id;
    var timeOfAuth = event.timestamp;

    // The 'ref' field is set in the 'Send to Messenger' plugin, in the 'data-ref'
    // The developer can set this to an arbitrary value to associate the
    // authentication callback with the 'Send to Messenger' click event. This is
    // a way to do account linking when the user clicks the 'Send to Messenger'
    // plugin.
    var passThroughParam = event.optin.ref;

    console.log("Received authentication for user %d and page %d with pass " +
    "through param '%s' at %d", senderID, recipientID, passThroughParam,
    timeOfAuth);

    // When an authentication is received, we'll send a message back to the sender
    // to let them know it was successful.
    sendTextMessage(senderID, "Authentication successful");
}

/*
* Message Event
*
* This event is called when a message is sent to your page. The 'message'
* object format can vary depending on the kind of message that was received.
* Read more at https://developers.facebook.com/docs/messenger-platform/webhook-reference/message-received
*
* For this example, we're going to echo any text that we get. If we get some
* special keywords ('button', 'generic', 'receipt'), then we'll send back
* examples of those bubbles to illustrate the special message bubbles we've
* created. If we receive a message with an attachment (image, video, audio),
* then we'll simply confirm that we've received the attachment.
*
*/
function receivedMessage(event) {
    var senderID = event.sender.id;
    var recipientID = event.recipient.id;
    var timeOfMessage = event.timestamp;
    var message = event.message;

    console.log("Received message for user %d and page %d at %d with message:",
    senderID, recipientID, timeOfMessage);
    console.log(JSON.stringify(message));

    var isEcho = message.is_echo;
    var messageId = message.mid;
    var appId = message.app_id;
    var metadata = message.metadata;

    // You may get a text or attachment but not both
    var messageText = message.text;
    var messageAttachments = message.attachments;
    var quickReply = message.quick_reply;

    if (isEcho) {
        // Just logging message echoes to console
        console.log("Received echo for message %s and app %d with metadata %s",
        messageId, appId, metadata);
        return;
    } else if (quickReply) {
        var quickReplyPayload = quickReply.payload;
        console.log("Quick reply for message %s with payload %s",
        messageId, quickReplyPayload);

        sendTextMessage(senderID, "Quick reply tapped");
        return;
    }

    if (messageText) {

        // If we receive a text message, check to see if it matches any special
        // keywords and send back the corresponding example. Otherwise, just echo
        // the text we received.
        switch (messageText) {
            case 'image':
            sendImageMessage(senderID);
            break;

            case 'gif':
            sendGifMessage(senderID);
            break;

            case 'audio':
            sendAudioMessage(senderID);
            break;

            case 'video':
            sendVideoMessage(senderID);
            break;

            case 'file':
            sendFileMessage(senderID);
            break;

            case 'button':
            sendButtonMessage(senderID);
            break;

            case 'generic':
            sendGenericMessage(senderID);
            break;

            case 'receipt':
            sendReceiptMessage(senderID);
            break;

            case 'quick reply':
            sendQuickReply(senderID);
            break;

            case 'read receipt':
            sendReadReceipt(senderID);
            break;

            case 'typing on':
            sendTypingOn(senderID);
            break;

            case 'typing off':
            sendTypingOff(senderID);
            break;

            case 'account linking':
            sendAccountLinking(senderID);
            break;

            default:
            sendTextMessage(senderID, messageText);
        }
    } else if (messageAttachments) {
        sendTextMessage(senderID, "Message with attachment received");
    }
}

/*
* Delivery Confirmation Event
*
* This event is sent to confirm the delivery of a message. Read more about
* these fields at https://developers.facebook.com/docs/messenger-platform/webhook-reference/message-delivered
*
*/
function receivedDeliveryConfirmation(event) {
    var senderID = event.sender.id;
    var recipientID = event.recipient.id;
    var delivery = event.delivery;
    var messageIDs = delivery.mids;
    var watermark = delivery.watermark;
    var sequenceNumber = delivery.seq;

    if (messageIDs) {
        messageIDs.forEach(function(messageID) {
            console.log("Received delivery confirmation for message ID: %s",
            messageID);
        });
    }

    console.log("All message before %d were delivered.", watermark);
}

/*
* Postback Event
*
* This event is called when a postback is tapped on a Structured Message.
* https://developers.facebook.com/docs/messenger-platform/webhook-reference/postback-received
*
*/
function receivedPostback(event) {
    var senderID = event.sender.id;
    var recipientID = event.recipient.id;
    var timeOfPostback = event.timestamp;

    // The 'payload' param is a developer-defined field which is set in a postback
    // button for Structured Messages.
    var payload = event.postback.payload;

    console.log("Received postback for user %d and page %d with payload '%s' " +
    "at %d", senderID, recipientID, payload, timeOfPostback);

    // When a postback is called, we'll send a message back to the sender to
    // let them know it was successful
    sendTextMessage(senderID, "Postback called");
}

/*
* Message Read Event
*
* This event is called when a previously-sent message has been read.
* https://developers.facebook.com/docs/messenger-platform/webhook-reference/message-read
*
*/
function receivedMessageRead(event) {
    var senderID = event.sender.id;
    var recipientID = event.recipient.id;

    // All messages before watermark (a timestamp) or sequence have been seen.
    var watermark = event.read.watermark;
    var sequenceNumber = event.read.seq;

    console.log("Received message read event for watermark %d and sequence " +
    "number %d", watermark, sequenceNumber);
}

/*
* Account Link Event
*
* This event is called when the Link Account or UnLink Account action has been
* tapped.
* https://developers.facebook.com/docs/messenger-platform/webhook-reference/account-linking
*
*/
function receivedAccountLink(event) {
    var senderID = event.sender.id;
    var recipientID = event.recipient.id;

    var status = event.account_linking.status;
    var authCode = event.account_linking.authorization_code;

    console.log("Received account link event with for user %d with status %s " +
    "and auth code %s ", senderID, status, authCode);
}

/*
* Send an image using the Send API.
*
*/
function sendImageMessage(recipientId) {
    var messageData = {
        recipient: {
            id: recipientId
        },
        message: {
            attachment: {
                type: "image",
                payload: {
                    url: SERVER_URL + "/assets/rift.png"
                }
            }
        }
    };

    callSendAPI(messageData);
}

/*
* Send a Gif using the Send API.
*
*/
function sendGifMessage(recipientId) {
    var messageData = {
        recipient: {
            id: recipientId
        },
        message: {
            attachment: {
                type: "image",
                payload: {
                    url: SERVER_URL + "/assets/instagram_logo.gif"
                }
            }
        }
    };

    callSendAPI(messageData);
}

/*
* Send audio using the Send API.
*
*/
function sendAudioMessage(recipientId) {
    var messageData = {
        recipient: {
            id: recipientId
        },
        message: {
            attachment: {
                type: "audio",
                payload: {
                    url: SERVER_URL + "/assets/sample.mp3"
                }
            }
        }
    };

    callSendAPI(messageData);
}

/*
* Send a video using the Send API.
*
*/
function sendVideoMessage(recipientId) {
    var messageData = {
        recipient: {
            id: recipientId
        },
        message: {
            attachment: {
                type: "video",
                payload: {
                    url: SERVER_URL + "/assets/allofus480.mov"
                }
            }
        }
    };

    callSendAPI(messageData);
}

/*
* Send a file using the Send API.
*
*/
function sendFileMessage(recipientId) {
    var messageData = {
        recipient: {
            id: recipientId
        },
        message: {
            attachment: {
                type: "file",
                payload: {
                    url: SERVER_URL + "/assets/test.txt"
                }
            }
        }
    };

    callSendAPI(messageData);
}

/*
* Send a text message using the Send API.
*
*/
function sendTextMessage(recipientId, messageText) {
    var messageData = {
        recipient: {
            id: recipientId
        },
        message: {
            text: messageText,
            metadata: "DEVELOPER_DEFINED_METADATA"
        }
    };

    callSendAPI(messageData);
}

/*
* Send a button message using the Send API.
*
*/
function sendButtonMessage(recipientId) {
    var messageData = {
        recipient: {
            id: recipientId
        },
        message: {
            attachment: {
                type: "template",
                payload: {
                    template_type: "button",
                    text: "This is test text",
                    buttons:[{
                        type: "web_url",
                        url: "https://www.oculus.com/en-us/rift/",
                        title: "Open Web URL"
                    }, {
                        type: "postback",
                        title: "Trigger Postback",
                        payload: "DEVELOPER_DEFINED_PAYLOAD"
                    }, {
                        type: "phone_number",
                        title: "Call Phone Number",
                        payload: "+16505551234"
                    }]
                }
            }
        }
    };

    callSendAPI(messageData);
}

/*
* Send a Structured Message (Generic Message type) using the Send API.
*
*/
function sendGenericMessage(recipientId) {
    var messageData = {
        recipient: {
            id: recipientId
        },
        message: {
            attachment: {
                type: "template",
                payload: {
                    template_type: "generic",
                    elements: [{
                        title: "rift",
                        subtitle: "Next-generation virtual reality",
                        item_url: "https://www.oculus.com/en-us/rift/",
                        image_url: SERVER_URL + "/assets/rift.png",
                        buttons: [{
                            type: "web_url",
                            url: "https://www.oculus.com/en-us/rift/",
                            title: "Open Web URL"
                        }, {
                            type: "postback",
                            title: "Call Postback",
                            payload: "Payload for first bubble",
                        }],
                    }, {
                        title: "touch",
                        subtitle: "Your Hands, Now in VR",
                        item_url: "https://www.oculus.com/en-us/touch/",
                        image_url: SERVER_URL + "/assets/touch.png",
                        buttons: [{
                            type: "web_url",
                            url: "https://www.oculus.com/en-us/touch/",
                            title: "Open Web URL"
                        }, {
                            type: "postback",
                            title: "Call Postback",
                            payload: "Payload for second bubble",
                        }]
                    }]
                }
            }
        }
    };

    callSendAPI(messageData);
}

/*
* Send a receipt message using the Send API.
*
*/
function sendReceiptMessage(recipientId) {
    // Generate a random receipt ID as the API requires a unique ID
    var receiptId = "order" + Math.floor(Math.random()*1000);

    var messageData = {
        recipient: {
            id: recipientId
        },
        message:{
            attachment: {
                type: "template",
                payload: {
                    template_type: "receipt",
                    recipient_name: "Peter Chang",
                    order_number: receiptId,
                    currency: "USD",
                    payment_method: "Visa 1234",
                    timestamp: "1428444852",
                    elements: [{
                        title: "Oculus Rift",
                        subtitle: "Includes: headset, sensor, remote",
                        quantity: 1,
                        price: 599.00,
                        currency: "USD",
                        image_url: SERVER_URL + "/assets/riftsq.png"
                    }, {
                        title: "Samsung Gear VR",
                        subtitle: "Frost White",
                        quantity: 1,
                        price: 99.99,
                        currency: "USD",
                        image_url: SERVER_URL + "/assets/gearvrsq.png"
                    }],
                    address: {
                        street_1: "1 Hacker Way",
                        street_2: "",
                        city: "Menlo Park",
                        postal_code: "94025",
                        state: "CA",
                        country: "US"
                    },
                    summary: {
                        subtotal: 698.99,
                        shipping_cost: 20.00,
                        total_tax: 57.67,
                        total_cost: 626.66
                    },
                    adjustments: [{
                        name: "New Customer Discount",
                        amount: -50
                    }, {
                        name: "$100 Off Coupon",
                        amount: -100
                    }]
                }
            }
        }
    };

    callSendAPI(messageData);
}

/*
* Send a message with Quick Reply buttons.
*
*/
function sendQuickReply(recipientId) {
    var messageData = {
        recipient: {
            id: recipientId
        },
        message: {
            text: "What's your favorite movie genre?",
            quick_replies: [
                {
                    "content_type":"text",
                    "title":"Action",
                    "payload":"DEVELOPER_DEFINED_PAYLOAD_FOR_PICKING_ACTION"
                },
                {
                    "content_type":"text",
                    "title":"Comedy",
                    "payload":"DEVELOPER_DEFINED_PAYLOAD_FOR_PICKING_COMEDY"
                },
                {
                    "content_type":"text",
                    "title":"Drama",
                    "payload":"DEVELOPER_DEFINED_PAYLOAD_FOR_PICKING_DRAMA"
                }
            ]
        }
    };

    callSendAPI(messageData);
}

/*
* Send a read receipt to indicate the message has been read
*
*/
function sendReadReceipt(recipientId) {
    console.log("Sending a read receipt to mark message as seen");

    var messageData = {
        recipient: {
            id: recipientId
        },
        sender_action: "mark_seen"
    };

    callSendAPI(messageData);
}

/*
* Turn typing indicator on
*
*/
function sendTypingOn(recipientId) {
    console.log("Turning typing indicator on");

    var messageData = {
        recipient: {
            id: recipientId
        },
        sender_action: "typing_on"
    };

    callSendAPI(messageData);
}

/*
* Turn typing indicator off
*
*/
function sendTypingOff(recipientId) {
    console.log("Turning typing indicator off");

    var messageData = {
        recipient: {
            id: recipientId
        },
        sender_action: "typing_off"
    };

    callSendAPI(messageData);
}

/*
* Send a message with the account linking call-to-action
*
*/
function sendAccountLinking(recipientId) {
    var messageData = {
        recipient: {
            id: recipientId
        },
        message: {
            attachment: {
                type: "template",
                payload: {
                    template_type: "button",
                    text: "Welcome. Link your account.",
                    buttons:[{
                        type: "account_link",
                        url: SERVER_URL + "/authorize"
                    }]
                }
            }
        }
    };

    callSendAPI(messageData);
}

/*
* Call the Send API. The message data goes in the body. If successful, we'll
* get the message id in a response
*
*/
function callSendAPI(messageData) {
    request({
        uri: 'https://graph.facebook.com/v2.6/me/messages',
        qs: { access_token: PAGE_ACCESS_TOKEN },
        method: 'POST',
        json: messageData

    }, function (error, response, body) {
        if (!error && response.statusCode == 200) {
            var recipientId = body.recipient_id;
            var messageId = body.message_id;

            if (messageId) {
                console.log("Successfully sent message with id %s to recipient %s",
                messageId, recipientId);
            } else {
                console.log("Successfully called Send API for recipient %s",
                recipientId);
            }
        } else {
            console.error("Failed calling Send API", response.statusCode, response.statusMessage, body.error);
        }
    });
}

// Start server
// Webhooks must be available via SSL with a certificate signed by a valid
// certificate authority.
app.listen(app.get('port'), function() {
    console.log('Node app is running on port', app.get('port'));
});

module.exports = app;
<|endoftext|>


<commit_before>/*
 Copyright (c) 2013, Yahoo! Inc. All rights reserved.
 Copyrights licensed under the New BSD License.
 See the accompanying LICENSE file for terms.
 */

'use strict';

exports.units = pureGridUnits;

// -----------------------------------------------------------------------------

// IE < 8 has issues with rounding, reducing the width slightly prevents the
// grid units from wrapping to the next line.
var OLD_IE_WIDTH_DELTA = -0.00031;

// AST of declarations of Pure's `.pure-u` styles that get applied to all of the
// generated grid units.
var UNIT_DECLARATIONS  = [
    {
        type    : 'declaration',
        property: 'display',
        value   : 'inline-block'
    },

    {
        type    : 'declaration',
        property: '*display',
        value   : 'inline'
    },

    {
        type    : 'declaration',
        property: 'zoom',
        value   : '1'
    },

    {
        type    : 'declaration',
        property: 'letter-spacing',
        value   : 'normal'
    },

    {
        type    : 'declaration',
        property: 'word-spacing',
        value   : 'normal'
    },

    {
        type    : 'declaration',
        property: 'vertical-align',
        value   : 'top'
    },

    {
        type    : 'declaration',
        property: 'text-rendering',
        value   : 'auto'
    }
];

function getUnitSelector(numerator, denominator) {
    if (denominator === 1) {
        return '.pure-u-1';
    }

    return '.pure-u-' + numerator + '-' + denominator;
}

function pureGridUnits(numUnits, options) {
    options || (options = {});

    // Include `*width` values for IE < 8 by default.
    var includeOldIEWidths = 'includeOldIEWidths' in options ?
            !!options.includeOldIEWidths : true;

    // Include reduced fractions by default; e.g., 2/4 and 1/2.
    var includeReducedFractions = 'includeReducedFractions' in options ?
            !!options.includeReducedFractions : true;

    function toPercentage(num) {
        num *= 100;
        return num.toFixed(num % 1 === 0 ? 0 : options.decimals || 4) + '%';
    }

    return function (style) {
        var unitRules     = [],
            unitSelectors = [],
            current       = 1,
            selectors, declarations, width, reduced;

        while (current <= numUnits) {
            selectors = [getUnitSelector(current, numUnits)];
            width     = current / numUnits;

            declarations = [{
                type    : 'declaration',
                property: 'width',
                value   : toPercentage(width)
            }];

            // Adds an additional selector for the reduced fraction if there is
            // one and the `includeReducedFractions` option is truthy.
            if (includeReducedFractions) {
                reduced = getReduced(current, numUnits);

                // Makes sure the faction has been reduced before adding another
                // selector for the current grid unit.
                if (reduced[0] !== current && reduced[1] !== numUnits) {
                    selectors.push(getUnitSelector(reduced[0], reduced[1]));
                }
            }

            // Adds an additonal `*width` declaration for IE < 8 if the current
            // unit < 100% width and the `includeOldIEWidths` option is truthy.
            if (includeOldIEWidths && width < 1) {
                // Updates the width value for the `*width` property to ensure
                // IE < 8's rounding issues don't break the grid.
                width += OLD_IE_WIDTH_DELTA;

                declarations.push({
                    type    : 'declaration',
                    property: '*width',
                    value   : toPercentage(width)
                });
            }

            // Stores rule for the current grid unit's `width` declarations
            // which will be later added to the CSS.
            unitRules.push({
                type        : 'rule',
                selectors   : selectors,
                declarations: declarations
            });

            // Stores selectors for the current grid unit which later will all
            // be combined with the `.pure-u` declarations and added to the CSS.
            unitSelectors.push.apply(unitSelectors, selectors);

            // Update numerator and process the next grid unit.
            current += 1;
        }

        // Adds rule that applies the `.pure-u` declarations to all of the grid
        // unit that were created in the process above.
        style.rules.push({
            type        : 'rule',
            selectors   : unitSelectors,
            declarations: UNIT_DECLARATIONS
        });

        // Adds all of the grid unit `width` rules to the CSS.
        style.rules.push.apply(style.rules, unitRules);
    };
}

// -- Utilities ----------------------------------------------------------------

function getGCD(a, b) {
    return b ? getGCD(b, a % b) : a;
}

function getReduced(numerator, denominator) {
    var gcd = getGCD(numerator, denominator);
    return [numerator / gcd, denominator / gcd];
}
<commit_msg>Add support for multiple unit sets and output de-dupped sorted selectors<commit_after>/*
 Copyright (c) 2013, Yahoo! Inc. All rights reserved.
 Copyrights licensed under the New BSD License.
 See the accompanying LICENSE file for terms.
 */

'use strict';

exports.units = pureGridUnits;

// -----------------------------------------------------------------------------

// IE < 8 has issues with rounding, reducing the width slightly prevents the
// grid units from wrapping to the next line.
var OLD_IE_WIDTH_DELTA = -0.00031;

// AST of declarations of Pure's `.pure-u` styles that get applied to all of the
// generated grid units.
var UNIT_DECLARATIONS  = [
    {
        type    : 'declaration',
        property: 'display',
        value   : 'inline-block'
    },

    {
        type    : 'declaration',
        property: '*display',
        value   : 'inline'
    },

    {
        type    : 'declaration',
        property: 'zoom',
        value   : '1'
    },

    {
        type    : 'declaration',
        property: 'letter-spacing',
        value   : 'normal'
    },

    {
        type    : 'declaration',
        property: 'word-spacing',
        value   : 'normal'
    },

    {
        type    : 'declaration',
        property: 'vertical-align',
        value   : 'top'
    },

    {
        type    : 'declaration',
        property: 'text-rendering',
        value   : 'auto'
    }
];

function getSelector(numerator, denominator) {
    if (denominator === 1) {
        return '.pure-u-1';
    }

    return '.pure-u-' + numerator + '-' + denominator;
}

function getSelectorFraction(selector) {
    var captures = selector.match(/^\.pure-u-(\d+)(?:-(\d+))?$/);

    return [
        parseInt(captures[1], 10),
        parseInt(captures[2], 10) || 1
    ];
}

function sortSelectors(a, b) {
    var aFrac = getSelectorFraction(a),
        bFrac = getSelectorFraction(b);

    // Sort by denominator first.
    if (aFrac[1] < bFrac[1]) { return -1; }
    if (aFrac[1] > bFrac[1]) { return 1; }

    // When the denominators are the same, sort by the numerator.
    if (aFrac[0] < bFrac[0]) { return -1; }
    if (aFrac[0] > bFrac[0]) { return 1; }

    return 0;
}

function pureGridUnits(units, options) {
    Array.isArray(units) || (units = [units]);

    options = extend({
        includeOldIEWidths     : true,
        includeReducedFractions: true
    }, options);

    function toPercentage(num) {
        num *= 100;
        return num.toFixed(num % 1 === 0 ? 0 : options.decimals || 4) + '%';
    }

    return function (style) {
        var rules     = {},
            selectors = {};

        function generateUnitRules(numUnits) {
            var current = 1,
                rule, selector, width, reduced;

            while (current <= numUnits) {
                width = current / numUnits;
                rule  = rules[width];

                if (!rule) {
                    // The rule's `selectors` are stored as an object for
                    // de-dupping.
                    rule = rules[width] = {
                        type        : 'rule',
                        selectors   : {},

                        declarations: [{
                            type    : 'declaration',
                            property: 'width',
                            value   : toPercentage(width)
                        }]
                    };

                    // Adds an additonal `*width` declaration for IE < 8 if the
                    // current unit < 100% width and the `includeOldIEWidths`
                    // option is truthy.
                    if (options.includeOldIEWidths && width < 1) {
                        // Updates the width value for the `*width` property to
                        // ensure IE < 8's rounding issues don't break the grid.
                        width += OLD_IE_WIDTH_DELTA;

                        rule.declarations.push({
                            type    : 'declaration',
                            property: '*width',
                            value   : toPercentage(width)
                        });
                    }
                }

                // Create and store the selectors, in de-dupped format.
                selector = getSelector(current, numUnits);
                rule.selectors[selector] = selectors[selector] = true;

                // Adds an additional selector for the reduced fraction if there
                // is one and the `includeReducedFractions` option is truthy.
                if (options.includeReducedFractions) {
                    reduced = getReduced(current, numUnits);

                    // Makes sure the faction has been reduced before adding
                    // another selector for the current grid unit.
                    if (reduced[0] !== current && reduced[1] !== numUnits) {
                        // Create and store the selectors, in de-dupped format.
                        selector = getSelector(reduced[0], reduced[1]);
                        rule.selectors[selector] = selectors[selector] = true;
                    }
                }

                // Update numerator and process the next grid unit.
                current += 1;
            }
        }

        // Takes the array of `units` and creates unique declarations and
        // selectors for each set.
        units.forEach(generateUnitRules);

        // Adds rule that applies the `.pure-u` declarations to all of the grid
        // unit that were created in the process above. The de-dupped selectors
        // are first sorted.
        style.rules.push({
            type        : 'rule',
            selectors   : Object.keys(selectors).sort(sortSelectors),
            declarations: UNIT_DECLARATIONS
        });

        // Adds all of the grid unit `width` rules to the CSS, and sorts each
        // rule's selectors before adding them to `style`.
        Object.keys(rules).sort().forEach(function (width) {
            var rule = rules[width];

            rule.selectors = Object.keys(rule.selectors).sort(sortSelectors);
            style.rules.push(rule);
        });
    };
}

// -- Utilities ----------------------------------------------------------------

function extend(obj) {
    Array.prototype.slice.call(arguments, 1).forEach(function (source) {
        if (!source) { return; }

        Object.keys(source).forEach(function (name) {
            obj[name] = source[name];
        });
    });

    return obj;
}

function getGCD(a, b) {
    return b ? getGCD(b, a % b) : a;
}

function getReduced(numerator, denominator) {
    var gcd = getGCD(numerator, denominator);
    return [numerator / gcd, denominator / gcd];
}
<|endoftext|>


<commit_before>/*jshint node: true */
'use strict';

var path = require('path');
var fs   = require('fs');
var mergeTrees = require('broccoli-merge-trees');
var browserify = require('broccoli-browserify');
var flatiron = require('broccoli-flatiron');
var snippetFinder = require('./snippet-finder');

module.exports = {
  name: 'Code Snippet Ember Component',

  snippetPaths: function() {
    return this.app.options.snippetPaths || ['snippets'];
  },

  snippetSearchPaths: function(){
    return this.app.options.snippetSearchPaths || ['app'];
  },

  treeForApp: function(tree){
    var snippets= mergeTrees(this.snippetPaths().filter(function(path){
      return fs.existsSync(path);
    }));

    snippets = mergeTrees(this.snippetSearchPaths().map(function(path){
      return snippetFinder(path);
    }).concat(snippets));

    snippets = flatiron(snippets, {
      outputFile: 'snippets.js'
    });

    return mergeTrees([tree, snippets]);
  },

  treeForVendor: function(tree){
    // Package up the highlight.js source from its node module.
    var src = this.treeGenerator(path.join(__dirname, 'node_modules', 'highlight.js'));
    var highlight = browserify(src, {
      outputFile: 'highlight.js',
      require: [['./lib/index.js', {expose: 'highlight.js'}]]
    });
    return mergeTrees([highlight, tree]);
  },

  included: function(app) {
    app.import('vendor/highlight.js');
    app.import('vendor/highlight-style.css');
  }
};
<commit_msg>npm v3 support<commit_after>/*jshint node: true */
'use strict';

var path = require('path');
var fs   = require('fs');
var mergeTrees = require('broccoli-merge-trees');
var browserify = require('broccoli-browserify');
var flatiron = require('broccoli-flatiron');
var snippetFinder = require('./snippet-finder');

module.exports = {
  name: 'Code Snippet Ember Component',

  snippetPaths: function() {
    return this.app.options.snippetPaths || ['snippets'];
  },

  snippetSearchPaths: function(){
    return this.app.options.snippetSearchPaths || ['app'];
  },

  treeForApp: function(tree){
    var snippets= mergeTrees(this.snippetPaths().filter(function(path){
      return fs.existsSync(path);
    }));

    snippets = mergeTrees(this.snippetSearchPaths().map(function(path){
      return snippetFinder(path);
    }).concat(snippets));

    snippets = flatiron(snippets, {
      outputFile: 'snippets.js'
    });

    return mergeTrees([tree, snippets]);
  },

  treeForVendor: function(tree){
    // Package up the highlight.js source from its node module.

    var src = this.treeGenerator(path.join(require.resolve('highlight.js'), '..', '..'));

    var highlight = browserify(src, {
      outputFile: 'highlight.js',
      require: [['./lib/index.js', {expose: 'highlight.js'}]]
    });
    return mergeTrees([highlight, tree]);
  },

  included: function(app) {
    app.import('vendor/highlight.js');
    app.import('vendor/highlight-style.css');
  }
};
<|endoftext|>


<commit_before>var d3 = require('d3')
var hook = require('d3-utils/lifecycle')
var ƒ = require('d3-utils/get')

var objectEqual = require('object-equal')
var origRaf = require('raf')
var raf = require('auto-abort')(origRaf, function (f) { return origRaf.cancel(f) })

var KEY_CODES = {
  UP: 38,
  DOWN: 40,
  ENTER: 13,
  ESC: 27
}

function indexOf (arr, obj) {
  for (var i = 0; i < arr.length; i++) {
    if (objectEqual(obj, arr[i])) return i
  }

  return -1
}

module.exports = function (attrs, queryCallback) {
  var emitter = d3.dispatch('change', 'error')

  var selectedIndex = 0
  var suggestionsData = []
  var hideSuggestionsTimer
  var isOpen

  var element = document.createElement('section')
  var $elm = d3.select(element)
  var input = document.createElement('input')
  var $input = d3.select(input)
  var suggestions = document.createElement('ol')
  var $suggestions = d3.select(suggestions)

  element.appendChild(input)
  element.appendChild(suggestions)

  $elm.classed({
    'autocomplete': true,
    'control': true
  })

  $input
      .classed('input', true)
      .attr({
        type: 'search',
        name: 'autocomplete',
        autocomplete: 'off',
        placeholder: attrs.placeholder || 'Search...'
      })
      .on('focus', completeQuery)
      .on('blur', hideSuggestions)
      .on('input', completeQuery)
      .on('keydown', keyboardRouter)
      .on('keyup', function () { d3.event.stopPropagation() })
      .on('keypress', function () { d3.event.stopPropagation() })

  $suggestions
      .classed('suggestions', true)
  close()

  return {
    element: element,
    on: on,

    add: add,
    delete: del,
    clear: clear,

    close: close,
    open: open,

    value: value
  }

  function completeQuery () {
    if (hideSuggestionsTimer) clearTimeout(hideSuggestionsTimer)
    $elm.classed('is-loading', true)

    queryCallback($input.property('value'), function (err, results) {
      $elm.classed('is-loading', false)
      if (err) return emitter.error(err)

      open()
      selectedIndex = 0

      suggestionsData = results

      raf(render)
    })
  }

  function render () {
    $suggestions.selectAll('li')
      .data(suggestionsData, JSON.stringify)
    .call(hook(
      function (enter) {
        enter.append('li')
            .on('click', change)
            .on('mouseover', function (_, i) {
              selectedIndex = i
              raf(render)
            })
            .text(ƒ('label'))
      },
      function (update) {
        update
            .classed('search-highlight', function (d, i) {
              return i === selectedIndex
            })
      },
      function (exit) {
        exit.remove()
      }
    ))
  }

  function hideSuggestions (delta) {
    if (hideSuggestionsTimer) return

    hideSuggestionsTimer = setTimeout(function () {
      close()
      hideSuggestionsTimer = null
    }, 100)
  }

  function keyboardRouter () {
    d3.event.stopPropagation()
    var keyCode = d3.event.keyCode
    if (keyCode === KEY_CODES.ENTER) {
      if (isOpen) {
        change(suggestionsData[selectedIndex])
        d3.event.preventDefault()
        close()
      }

      return
    }

    if (keyCode === KEY_CODES.ESC) {
      selectedIndex = 0
      close()
      return
    }

    if (keyCode === KEY_CODES.UP) {
      d3.event.preventDefault()
      selectedIndex = mod(selectedIndex - 1, suggestionsData.length)
      raf(render)
      return
    }

    if (keyCode === KEY_CODES.DOWN) {
      d3.event.preventDefault()
      selectedIndex = mod(selectedIndex + 1, suggestionsData.length)
      raf(render)
      return
    }
  }

  function on () {
    return emitter.on.apply(emitter, arguments)
  }

  function add (obj, before) {
    if (Array.isArray(obj)) suggestionsData.splice.apply(suggestionsData, [indexOf(suggestionsData, before), 0].concat(obj))
    else suggestionsData.splice(indexOf(suggestionsData, before), 0, obj)
    raf(render)
  }

  function del (obj) {
    var idx = indexOf(suggestionsData, obj)

    if (idx > -1) {
      suggestionsData.splice(idx, 1)
      raf(render)
    }
  }

  function clear () {
    suggestionsData = []
    raf(render)
  }

  function close () {
    isOpen = false
    $suggestions.style('display', 'none')
  }

  function open () {
    isOpen = true
    $suggestions.style('display', null)
  }

  function change (d) {
    $input.property('value', d.label)
    emitter.change(d)
  }

  function value (val) {
    $input.property('value', val)
  }
}

function mod (n, p) {
  return ((n % p) + p) % p
}
<commit_msg>Namespace event listeners (D3 detail)<commit_after>var d3 = require('d3')
var hook = require('d3-utils/lifecycle')
var ƒ = require('d3-utils/get')

var objectEqual = require('object-equal')
var origRaf = require('raf')
var raf = require('auto-abort')(origRaf, function (f) { return origRaf.cancel(f) })

var KEY_CODES = {
  UP: 38,
  DOWN: 40,
  ENTER: 13,
  ESC: 27
}

function indexOf (arr, obj) {
  for (var i = 0; i < arr.length; i++) {
    if (objectEqual(obj, arr[i])) return i
  }

  return -1
}

module.exports = function (attrs, queryCallback) {
  var emitter = d3.dispatch('change', 'error')

  var selectedIndex = 0
  var suggestionsData = []
  var hideSuggestionsTimer
  var isOpen

  var element = document.createElement('section')
  var $elm = d3.select(element)
  var input = document.createElement('input')
  var $input = d3.select(input)
  var suggestions = document.createElement('ol')
  var $suggestions = d3.select(suggestions)

  element.appendChild(input)
  element.appendChild(suggestions)

  $elm.classed({
    'autocomplete': true,
    'control': true
  })

  $input
      .classed('input', true)
      .attr({
        type: 'search',
        name: 'autocomplete',
        autocomplete: 'off',
        placeholder: attrs.placeholder || 'Search...'
      })
      .on('focus.autocomplete', completeQuery)
      .on('blur.autocomplete', hideSuggestions)
      .on('input.autocomplete', completeQuery)
      .on('keydown.autocomplete', keyboardRouter)
      .on('keyup.autocomplete', function () { d3.event.stopPropagation() })
      .on('keypress.autocomplete', function () { d3.event.stopPropagation() })

  $suggestions
      .classed('suggestions', true)
  close()

  return {
    element: element,
    on: on,

    add: add,
    delete: del,
    clear: clear,

    close: close,
    open: open,

    value: value
  }

  function completeQuery () {
    if (hideSuggestionsTimer) clearTimeout(hideSuggestionsTimer)
    $elm.classed('is-loading', true)

    queryCallback($input.property('value'), function (err, results) {
      $elm.classed('is-loading', false)
      if (err) return emitter.error(err)

      open()
      selectedIndex = 0

      suggestionsData = results

      raf(render)
    })
  }

  function render () {
    $suggestions.selectAll('li')
      .data(suggestionsData, JSON.stringify)
    .call(hook(
      function (enter) {
        enter.append('li')
            .on('click.autocomplete', change)
            .on('mouseover.autocomplete', function (_, i) {
              selectedIndex = i
              raf(render)
            })
            .text(ƒ('label'))
      },
      function (update) {
        update
            .classed('search-highlight', function (d, i) {
              return i === selectedIndex
            })
      },
      function (exit) {
        exit.remove()
      }
    ))
  }

  function hideSuggestions (delta) {
    if (hideSuggestionsTimer) return

    hideSuggestionsTimer = setTimeout(function () {
      close()
      hideSuggestionsTimer = null
    }, 100)
  }

  function keyboardRouter () {
    d3.event.stopPropagation()
    var keyCode = d3.event.keyCode
    if (keyCode === KEY_CODES.ENTER) {
      if (isOpen) {
        change(suggestionsData[selectedIndex])
        d3.event.preventDefault()
        close()
      }

      return
    }

    if (keyCode === KEY_CODES.ESC) {
      selectedIndex = 0
      close()
      return
    }

    if (keyCode === KEY_CODES.UP) {
      d3.event.preventDefault()
      selectedIndex = mod(selectedIndex - 1, suggestionsData.length)
      raf(render)
      return
    }

    if (keyCode === KEY_CODES.DOWN) {
      d3.event.preventDefault()
      selectedIndex = mod(selectedIndex + 1, suggestionsData.length)
      raf(render)
      return
    }
  }

  function on () {
    return emitter.on.apply(emitter, arguments)
  }

  function add (obj, before) {
    if (Array.isArray(obj)) suggestionsData.splice.apply(suggestionsData, [indexOf(suggestionsData, before), 0].concat(obj))
    else suggestionsData.splice(indexOf(suggestionsData, before), 0, obj)
    raf(render)
  }

  function del (obj) {
    var idx = indexOf(suggestionsData, obj)

    if (idx > -1) {
      suggestionsData.splice(idx, 1)
      raf(render)
    }
  }

  function clear () {
    suggestionsData = []
    raf(render)
  }

  function close () {
    isOpen = false
    $suggestions.style('display', 'none')
  }

  function open () {
    isOpen = true
    $suggestions.style('display', null)
  }

  function change (d) {
    $input.property('value', d.label)
    emitter.change(d)
  }

  function value (val) {
    $input.property('value', val)
  }
}

function mod (n, p) {
  return ((n % p) + p) % p
}
<|endoftext|>


<commit_before>$(window).load(function() {
  var sglEnabledUserNames = [
    'Ariutta',
    'AlexanderPico',
    'Khanspers',
    'bgood',
  ];
  if (!!window.SGL && sglEnabledUserNames.indexOf(window.wgUserName) > -1) {
    var token;
    var myParam = location.search.split('token=')[1];
    if (myParam != undefined) {
      token = myParam.split('&')[0];
    } else {
      console.error("ScienceGameLab Error: you are not logged in with a valid token");
    }

    function submitSGLActivity(type){
      var activity_body = {
          _t: 'activity.create',
          accounttoken: token,
          details: { gameId: '5751ed90e4b050b536ba7a03' },
          activity: type,
          time: (new Date()).toTimeString()
      };
      window.SGL.create_activity(activity_body, function(err, response) {
        // TODO does this method use the node callback style?
        console.log('SGL err');
        console.log(err);
        console.log('SGL response');
        console.log(response);
      });
    }

    var editorOpenState = false;
    $('#pvjs-container').bind('DOMSubtreeModified', function(e) {
      var annotationElement = $('.annotation.ui-draggable');
      var updatedEditorOpenState = annotationElement.hasClass('editor-open');
      if (editorOpenState && !updatedEditorOpenState) {
        console.log('Sending update to SGL');
        submitSGLActivity('easy');
      }
      editorOpenState = updatedEditorOpenState;
    });
  }
});
<commit_msg>Case-insensitive for user names<commit_after>$(window).load(function() {
  var currentUserNameLC = !!window.wgUserName ?  String(window.wgUserName).toLowerCase() : window.wgUserName;
  var sglEnabledLCUserNames = [
    'Ariutta',
    'AlexanderPico',
    'Khanspers',
    'bgood',
  ].map(function(userName) {
    return userName.toLowerCase();
  });
  if (!!window.SGL && sglEnabledLCUserNames.indexOf(currentUserNameLC) > -1) {
    var token;
    var myParam = location.search.split('token=')[1];
    if (myParam != undefined) {
      token = myParam.split('&')[0];
    } else {
      console.error("ScienceGameLab Error: you are not logged in with a valid token");
    }

    function submitSGLActivity(type){
      var activity_body = {
          _t: 'activity.create',
          accounttoken: token,
          details: { gameId: '5751ed90e4b050b536ba7a03' },
          activity: type,
          time: (new Date()).toTimeString()
      };
      window.SGL.create_activity(activity_body, function(err, response) {
        // TODO does this method use the node callback style?
        console.log('SGL err');
        console.log(err);
        console.log('SGL response');
        console.log(response);
      });
    }

    var editorOpenState = false;
    $('#pvjs-container').bind('DOMSubtreeModified', function(e) {
      var annotationElement = $('.annotation.ui-draggable');
      var updatedEditorOpenState = annotationElement.hasClass('editor-open');
      if (editorOpenState && !updatedEditorOpenState) {
        console.log('Sending update to SGL');
        submitSGLActivity('easy');
      }
      editorOpenState = updatedEditorOpenState;
    });
  }
});
<|endoftext|>