Skip to content

Instantly share code, notes, and snippets.

@dcolucci
Last active November 5, 2015 07:37
Show Gist options
  • Select an option

  • Save dcolucci/c46bfdfb3265873457d7 to your computer and use it in GitHub Desktop.

Select an option

Save dcolucci/c46bfdfb3265873457d7 to your computer and use it in GitHub Desktop.

First you need to install npm and then install required node modules:

  1. npm install jsdom
  2. npm install fs
  3. npm install underscore

to run the script, run node index.js. It will output the output.html file, which can be viewed in a browser.

<html>
<body>
<head>
<style>
html, body {
font-family: sans-serif;
}
.location-list-header {
margin-top: 50px;
font-size: 30px;
text-align: center;
text-transform: uppercase;
letter-spacing: .2rem;
color: #777777;
}
.location-lists {
text-align: center;
}
.criteria-name {
text-transform: uppercase;
color: #333333;
letter-spacing: .15rem;
}
.location-list {
list-style: none;
text-align: center;
color: #555555;
}
</style>
</head>
<h1 class="location-list-header">Instarate</h1>
<div class="location-lists"></div>
</body>
</html>
// In order to run the script, the following node modules
// must be installed
var jsdom = require('jsdom');
var fs = require('fs');
var _ = require('underscore');
// Create a JS DOM so that we can easily make AJAX calls and
// build up an HTML file using jQuery
jsdom.env({
html: fs.readFileSync('./index.html'),
// pull in jQuery from their CDN
scripts: ['http://code.jquery.com/jquery.js'],
done: function (errors, window) {
var $ = window.$;
var instarate = {
// Instagram API client ID
_clientId: '77e63572590f425cbf5005e410100617',
// Instagram API base endpoint
_apiEndpoint: 'https://api.instagram.com/v1',
// Intagram API media search endpoint
_mediaSearchEndpoint: '/media/search',
// Stores location coordinates of locations to compare in Instarate
// Provide as list of lat / lng coords with specified decimal precision
// Can provide any number of coordinate pairs per location since Instagram
// API can only search within 5km radius of each point
locationCoordinates: {
sanFrancisco: [
{ lat: '37.765992', lng: '-122.479864' },
{ lat: '37.778405', lng: '-122.423648' }
],
seattle: [
{ lat: '47.617238', lng: '-122.319911' },
{ lat: '47.652527', lng: '-122.306823' }
]
// etc
},
// Stores search criteria keywords that are used for ranking the locations
// Each key is a metric, and value is a list of tags in media objects
// that should count the post for the metric
searchCriteriaKeywords: {
outdoorsy: [
'hike',
'hiking',
'nature',
'outdoors'
],
dogFriendly: [
'pooch',
'woof',
'dog',
'dogs'
]
},
locationRankings: {},
rawMediaObjects: {},
filteredMediaObjects: {},
oldestMediaObjectTimestamps: {
sanFrancisco: null,
seattle: null
},
// GET request to Instagram media search endpoint for specified lat / lng
// Save results to this.rawMediaObjects
getMediaObjectsForLatLng: function (args) {
var url = this._apiEndpoint + this._mediaSearchEndpoint;
var callback = args.callback;
var location = args.location;
var params = {
lat: args.lat,
lng: args.lng,
distance: 5000,
max_timestamp: args.maxTimestamp,
client_id: this._clientId
};
$.ajax({
url: url,
data: params,
success: function (data) {
if (data.meta && data.meta.code === 200) {
data.data.forEach( function (mediaObject) {
// let's only save media objects with tags
if (mediaObject.tags.length > 0) {
if (this.rawMediaObjects[location]) {
this.rawMediaObjects[location].push(mediaObject);
} else {
this.rawMediaObjects[location] = [mediaObject];
}
if (mediaObject.created_time < this.oldestMediaObjectTimestamps[location]) {
this.oldestMediaObjectTimestamps[location] = mediaObject.created_time;
}
}
}.bind(this));
callback();
}
}.bind(this),
error: function (data) {
console.log('*** Error response from Instagram API ' + url + ' ***');
console.log(data);
}
});
},
// Kick off a callback chain of methods to retrieve media objects
// for the lat / lng coordinates specified in this.locationCoordinates
// for location
getMediaObjectsForLocation: function (location, outerCallback) {
var callChain = [outerCallback];
var locationList = this.locationCoordinates[location];
var maxTimestamp = this.oldestMediaObjectTimestamps[location];
for (var i = 0; i < locationList.length; i++) {
var callback = callChain[0] || function () {};
var coords = locationList[i];
var args = {
location: location,
lat: coords.lat,
lng: coords.lng,
maxTimestamp: maxTimestamp,
callback: callback
}
callChain.unshift(this.getMediaObjectsForLatLng.bind(this, args));
}
callChain[0]();
},
// Kick off a callback chain of methods to retrieve media objects
// for all locations specified in this.locationCoordinates
getMediaObjectsForAllLocations: function (outerCallback) {
var callChain = [outerCallback];
Object.keys(this.locationCoordinates).forEach( function (location) {
var callback = callChain[0] || function () {};
callChain.unshift(this.getMediaObjectsForLocation.bind(this, location, callback));
}.bind(this));
callChain[0]();
},
// Organize this.rawMediaObjects in to filtered sets
// We create keys on this.filteredMediaObjects for each city
// With sub-keys for the specified search criteria keywords
filterMediaObjectsBySearchCriteria: function () {
Object.keys(this.searchCriteriaKeywords).forEach ( function (criteria) {
var keywordList = this.searchCriteriaKeywords[criteria];
Object.keys(this.rawMediaObjects).forEach( function (location) {
this.rawMediaObjects[location].forEach( function (mediaObject) {
// should all be lowercase
if (_.intersection(keywordList, mediaObject.tags).length > 0) {
var locationResults = this.filteredMediaObjects[location];
if (locationResults) {
if (locationResults[criteria]) {
locationResults[criteria].push(mediaObject);
} else {
locationResults[criteria] = [mediaObject];
}
} else {
locationResults = {};
locationResults[criteria] = [mediaObject];
this.filteredMediaObjects[location] = locationResults;
}
}
}.bind(this));
}.bind(this));
}.bind(this));
},
// Sort locations into ranked lists in this.locationRankings
// Each key is a criteria and the list is the order in which each location
// most represents the criteria
rankLocations: function () {
Object.keys(this.searchCriteriaKeywords).forEach( function (criteria) {
var criteriaRanking = [];
Object.keys(this.filteredMediaObjects).forEach( function (location) {
var filteredMediaObjectsForLocation = this.filteredMediaObjects[location];
var count = 0;
if (filteredMediaObjectsForLocation && filteredMediaObjectsForLocation[criteria]) {
count = this.filteredMediaObjects[location][criteria].length;
}
criteriaRanking.push([location, count]);
}.bind(this));
this.locationRankings[criteria] = _.sortBy(criteriaRanking, function (locPair) {
return -1 * locPair[1]; // sort desc
});
}.bind(this));
},
// Write the ranked locations to the DOM
writeLocationRankingsToDom: function (callback) {
var $locationLists = $('.location-lists');
Object.keys(this.locationRankings).forEach( function (criteria) {
$locationLists.append($('<h2>').addClass('criteria-name').html(criteria));
$locationLists.append($('<ol>').addClass('location-list')
.addClass('location-list-' + criteria));
this.locationRankings[criteria].forEach( function (location) {
$('.location-list-' + criteria).append($('<li>').html(location[0]));
});
}.bind(this));
if (callback) callback();
},
// for testing
writeFilteredMediaObjectsToDom: function (callback) {
var $list = $('.location-lists');
Object.keys(this.filteredMediaObjects).forEach( function (location) {
$list.append($('<div>').addClass('location-name').html(location));
var filteredMediaObjectsForLocation = this.filteredMediaObjects[location];
Object.keys(filteredMediaObjectsForLocation).forEach( function (criteria) {
var criteriaMediaObjects = filteredMediaObjectsForLocation[criteria];
criteriaMediaObjects.forEach( function (mediaObject) {
var date = new Date(mediaObject.created_time * 1000);
var tags = mediaObject.tags.join();
var $li = $('<div>').html('' + date + ': ' + tags);
$list.append($li);
});
});
}.bind(this));
if (callback) callback();
},
// for testing
writeRawMediaObjectsToDom: function (callback) {
var $list = $('.location-lists');
Object.keys(this.rawMediaObjects).forEach( function (location) {
$list.append($('<div>').addClass('location-name').html(location));
this.rawMediaObjects[location].forEach( function (mediaObject) {
var date = new Date(mediaObject.created_time * 1000);
var tags = mediaObject.tags.join();
var $li = $('<div>').html('' + date + ': ' + tags);
$list.append($li);
});
}.bind(this));
if (callback) callback();
},
// Write the contents of the 'DOM' to output.html
writeFileContents: function () {
var newContents = '<html>' + $('html').html() + '</html>';
fs.writeFileSync('output.html', newContents, {}, function (error) {
if (error) throw error;
});
},
// Intended to be used as a callback once data has been retrieved from API
// Run the sequence of processing required to output lists
processData: function () {
this.filterMediaObjectsBySearchCriteria();
this.rankLocations();
//this.writeRawMediaObjectsToDom(this.writeFileContents);
//this.writeFilteredMediaObjectsToDom(this.writeFileContents);
this.writeLocationRankingsToDom(this.writeFileContents);
}
};
// kick off the logic
var callback = instarate.processData.bind(instarate);
instarate.getMediaObjectsForAllLocations(callback);
}
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment