Skip to content

Instantly share code, notes, and snippets.

@surjikal
Last active August 29, 2015 14:02
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save surjikal/78be475b6e3c0aa6d319 to your computer and use it in GitHub Desktop.
Save surjikal/78be475b6e3c0aa6d319 to your computer and use it in GitHub Desktop.
Sears Hackathon
_ = require 'lodash'
ent = require 'ent'
assert = require 'assert'
Promise = require 'bluebird'
SearsAPI = require './sears-api'
CONSUMER_KEY = '<your key here>'
getTopItems = (store, api) ->
mapCategory = (category) ->
category.VerticalName
parsePrice = (price) ->
throw new Error("missing price") if not price
return price if not _.isNaN(parseFloat(price))
price = price.replace(',','').replace(' ', '').match(/(\$\d+\.\d\d)/)[0]
throw new Error("price parse error") if not price
price = price.replace('$', '')
return parseFloat(price).toString() if not _.isNaN(parseFloat(price))
throw new Error("price parse error")
mapItem = (item) ->
id: item.Id.SkuPartNumber or item.Id.PartNumber
brand: ent.decode item.Description.BrandName or ''
name: ent.decode item.Description.Name
price: parsePrice (item.Price.DisplayPrice or item.Availability.FreeShippingThreshold)
_orig: JSON.stringify(item)
_store: store
fetchTopItems = (category) ->
console.error "--> fetching to items for category `#{category}`"
api.topItems(
category: category
startIndex: '0'
endIndex: '100'
).then (items) ->
return [] if not items
items.map (item) ->
item = mapItem(item)
item.category = category
item.price = "10.00" if item.id is '00344821000'
Object.keys(item).forEach (key) ->
assert not _.isUndefined(item[key]), "Item key `#{key}` is undefined for item: #{JSON.stringify(item)}"
Object.keys(item).reduce ((result, key) ->
result[key] = item[key].toLowerCase() if key isnt '_orig'
return result
), {}
api.categories()
.then (categories) ->
categories = categories.map(mapCategory)
categories = _.difference categories, ['Automotive', 'Food & Grocery', 'Seasonal', 'Shipping', 'Gifts']
# Merge all the item sets together, preserving rank
Promise.all(categories.map(fetchTopItems))
.then (itemSets) ->
result = []
while itemSets.reduce(((sum, x) -> sum + x.length), 0) > 0
nextItemSets = []
itemSets.forEach (itemSet) ->
item = itemSet.shift()
result.push(item) if item
nextItemSets.push(itemSet) if itemSet.length isnt 0
itemSets = nextItemSets
return result
apis =
sears: new SearsAPI({store:'sears', consumerKey:CONSUMER_KEY})
kmart: new SearsAPI({store:'kmart', consumerKey:CONSUMER_KEY})
stores = Object.keys(apis)
result = []
allTopItems = Promise.all stores.map (store) ->
getTopItems(store, apis[store]).then (items) ->
result = result.concat items
return items
allTopItems.then ->
result = _.values result.reduce ((seen, item) ->
seen[item.id] = item if not result[item.id]
return seen
), {}
return _.groupBy result, (x) ->
store = x._store
delete x._store
return store
.then (result) ->
console.log JSON.stringify(result, null, 2)
{
"name": "sears",
"version": "0.0.42",
"description": "",
"main": "app.coffee",
"author": "42",
"license": "Public Domain",
"dependencies": {
"bluebird": "~2.0.6",
"lodash": "~2.4.1",
"request": "~2.36.0",
"underscore.string": "~2.3.3",
"assert": "~1.1.1",
"ent": "~2.0.0"
}
}
_ = require 'lodash'
_.str = require 'underscore.string'
assert = require 'assert'
request = require 'request'
Promise = require 'bluebird'
class SearsAPI
headers:
"Pragma": "no-cache"
"Accept-Encoding": ""
"Accept-Language": "en-US,en;q=0.8"
"User-Agent": "42.crawler.sears"
"Accept": "application/json, text/javascript, */*; q=0.01"
"Connection": "keep-alive"
"Cache-Control": "no-cache"
baseUrl: 'http://api.developer.sears.com'
STORES: {'sears', 'kmart', 'mygopher'}
constructor: ({@consumerKey, @store} = {}) ->
throw new ReferenceError("Missing required `consumerKey` property.") if not @consumerKey
throw new ReferenceError("Missing required `storeName` property.") if not @store
throw new ReferenceError("Store `#{@store}` is not supported") if not (@store.toLowerCase() in Object.keys(@STORES))
categories: (options = {}) =>
@call("/v2.1/products/browse/categories/#{@store}/json", options)
.then (result) -> result.SearchResults.Verticals
topItems: (options = {}) =>
throw new ReferenceError("Missing required `category` property.") if not options.category
@call("/v2.1/products/browse/topSellers/#{@store}/json/searchType/unit", options)
.then (result) -> result.SearchResults.Products
call: (endpoint, options = {}) ->
escape = encodeURIComponent
queryString = do =>
options = JSON.parse(JSON.stringify(options))
options.apikey = @consumerKey
return Object.keys(options)
.filter((key) -> options[key] isnt undefined)
.map((key) -> [_.str.underscored(key), escape(options[key])])
.map((pair) -> pair.join('='))
.join('&')
requestOptions =
method: 'GET'
json: true
url: "#{@baseUrl}#{endpoint}?#{queryString}"
headers: @headers
deferred = Promise.defer()
request requestOptions, (err, res, body) ->
return deferred.reject(err) if err
return deferred.reject(body) if res.statusCode isnt 200
return deferred.resolve(body)
return deferred.promise
module.exports = SearsAPI
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment