From 6e012b13c25a89b40c94e3446c090df5350b26a3 Mon Sep 17 00:00:00 2001 From: sarang87 Date: Sun, 6 Sep 2020 13:41:06 -0400 Subject: [PATCH 1/2] Changed index.js, used an older version of index.js as this works perfectly on my local machine and changed some parts to use different css classes from google-scholar web page to retrieve the result set with cited count and related count correctly --- index.js | 147 +++++++++++++++++-------------------------------------- 1 file changed, 45 insertions(+), 102 deletions(-) diff --git a/index.js b/index.js index 7c1189e..6bfd8ac 100644 --- a/index.js +++ b/index.js @@ -4,16 +4,8 @@ let scholar = (function () { let request = require('request') let cheerio = require('cheerio') let striptags = require('striptags') - const throttledQueue = require('throttled-queue') - // 1 per 200 ms ~= 5/s per - // https://developers.google.com/webmaster-tools/search-console-api-original/v3/limits - - const perSecThrottle = throttledQueue(5, 1000) - const perMinThrottle = throttledQueue(200, 60 * 1000) - const RESULTS_PER_PAGE = 10 - - const GOOGLE_SCHOLAR_URL = 'https://scholar.google.com/scholar?hl=en&q=' + const GOOGLE_SCHOLAR_URL = 'https://scholar.google.com/scholar?q=' const GOOGLE_SCHOLAR_URL_PREFIX = 'https://scholar.google.com' const ELLIPSIS_HTML_ENTITY = '…' @@ -21,10 +13,6 @@ let scholar = (function () { const CITATION_COUNT_PREFIX = 'Cited by ' const RELATED_ARTICLES_PREFIX = 'Related articles' - const STATUS_CODE_FOR_RATE_LIMIT = 503 - const STATUS_MESSAGE_FOR_RATE_LIMIT = 'Service Unavailable' - const STATUS_MESSAGE_BODY = 'This page appears when Google automatically detects requests coming from your computer network which appear to be in violation of the Terms of Service. The block will expire shortly after those requests stop.' - // regex with thanks to http://stackoverflow.com/a/5917250/1449799 const RESULT_COUNT_RE = /\W*((\d+|\d{1,3}(,\d{3})*)(\.\d+)?) results/ @@ -33,15 +21,11 @@ let scholar = (function () { if (error) { reject(error) } else if (response.statusCode !== 200) { - if (response.statusCode === STATUS_CODE_FOR_RATE_LIMIT && response.statusMessage === STATUS_MESSAGE_FOR_RATE_LIMIT && response.body.indexOf(STATUS_MESSAGE_BODY) > -1) { - reject(new Error('you are being rate-limited by google. you have made too many requests too quickly. see: https://support.google.com/websearch/answer/86640')) - } else { - reject(new Error('expected statusCode 200 on http response, but got: ' + response.statusCode)) - } + reject('expected statusCode 200 on http response, but got', response.statusCode) } else { let $ = cheerio.load(html) - let results = $('.gs_r') + let results = $('.gs_ri') let resultCount = 0 let nextUrl = '' let prevUrl = '' @@ -54,43 +38,59 @@ let scholar = (function () { let processedResults = [] results.each((i, r) => { - $(r).find('.gs_ri h3 span').remove() - let title = $(r).find('.gs_ri h3').text().trim() - let url = $(r).find('.gs_ri h3 a').attr('href') - let authorNamesHTMLString = $(r).find('.gs_ri .gs_a').html() + $(r).find('h3 span').remove() + let title = $(r).find('h3').text().trim() + let url = $(r).find('h3 a').attr('href') + let authorNamesHTMLString = $(r).find('.gs_a').html() let etAl = false let etAlBegin = false let authors = [] - let description = $(r).find('.gs_ri .gs_rs').text() - let footerLinks = $(r).find('.gs_ri .gs_fl a') + let description = $(r).find('.gs_rs').text() + console.log("\n" + description + "\n") + let footerLinks = $(r).find('.gs_ri .gs_fl') let citedCount = 0 let citedUrl = '' let relatedUrl = '' - let pdfUrl = $($(r).find('.gs_ggsd a')[0]).attr('href') - + + // modified code path to extract scholar url from foorerLinks[0] + //console.log($(footerLinks[0]).html() +"\t\t***") + let alist = [] + $(footerLinks[0]).find('a').each(function (index, element) { + alist.push($(element).attr('href')); + }); + //console.log(alist[3] +"\t\txxxx\n\n") + let scholar_url = alist[2] + let related_url = alist[3] + + if ($(footerLinks[0]).text().indexOf(CITATION_COUNT_PREFIX) >= 0) { + citedCount = $(footerLinks[0]).text().substr(CITATION_COUNT_PREFIX.length) } - if ($(footerLinks[0]).attr && - $(footerLinks[0]).attr('href') && - $(footerLinks[0]).attr('href').length > 0) { - citedUrl = GOOGLE_SCHOLAR_URL_PREFIX + $(footerLinks[0]).attr('href') - } + //if ($(footerLinks[2]).attr && + // $(footerLinks[2]).attr('href') && + // $(footerLinks[2]).attr('href').length > 0) { + // console.log( $(footerLinks[2]).attr('href')) + // citedUrl = GOOGLE_SCHOLAR_URL_PREFIX + $(footerLinks[2]).attr('href') + //} + citedUrl = GOOGLE_SCHOLAR_URL_PREFIX + scholar_url + relatedUrl = GOOGLE_SCHOLAR_URL_PREFIX + related_url if (footerLinks && footerLinks.length && footerLinks.length > 0) { if ($(footerLinks[0]).text && $(footerLinks[0]).text().indexOf(CITATION_COUNT_PREFIX) >= 0) { - citedCount = $(footerLinks[0]).text().substr(CITATION_COUNT_PREFIX.length) + //console.log("Length:" + $(footerLinks[0]).text().slice(CITATION_COUNT_PREFIX.length+2,CITATION_COUNT_PREFIX.length+5)) + citedCount = $(footerLinks[0]).text().slice(CITATION_COUNT_PREFIX.length+2,CITATION_COUNT_PREFIX.length+5) } - if ($(footerLinks[1]).text && - $(footerLinks[1]).text().indexOf(RELATED_ARTICLES_PREFIX) >= 0 && - $(footerLinks[1]).attr && - $(footerLinks[1]).attr('href') && - $(footerLinks[1]).attr('href').length > 0) { - relatedUrl = GOOGLE_SCHOLAR_URL_PREFIX + $(footerLinks[1]).attr('href') - } + //if ($(footerLinks[1]).text && + // $(footerLinks[1]).text().indexOf(RELATED_ARTICLES_PREFIX) >= 0 && + // $(footerLinks[1]).attr && + // $(footerLinks[1]).attr('href') && + // $(footerLinks[1]).attr('href').length > 0) { + // relatedUrl = GOOGLE_SCHOLAR_URL_PREFIX + $(footerLinks[1]).attr('href') + // } } if (authorNamesHTMLString) { let cleanString = authorNamesHTMLString.substr(0, authorNamesHTMLString.indexOf(' - ')) @@ -132,8 +132,7 @@ let scholar = (function () { description: description, citedCount: citedCount, citedUrl: citedUrl, - relatedUrl: relatedUrl, - pdf: pdfUrl + relatedUrl: relatedUrl }) }) @@ -156,29 +155,13 @@ let scholar = (function () { prevUrl: prevUrl, next: function () { let p = new Promise(function (resolve, reject) { - perMinThrottle(() => { - perSecThrottle(() => { - var requestOptions = { - jar: true - } - requestOptions.url = nextUrl - request(requestOptions, scholarResultsCallback(resolve, reject)) - }) - }) + request(nextUrl, scholarResultsCallback(resolve, reject)) }) return p }, previous: function () { let p = new Promise(function (resolve, reject) { - perMinThrottle(() => { - perSecThrottle(() => { - var requestOptions = { - jar: true - } - requestOptions.url = prevUrl - request(requestOptions, scholarResultsCallback(resolve, reject)) - }) - }) + request(prevUrl, scholarResultsCallback(resolve, reject)) }) return p } @@ -189,53 +172,13 @@ let scholar = (function () { function search (query) { let p = new Promise(function (resolve, reject) { - perMinThrottle(() => { - perSecThrottle(() => { - var requestOptions = { - jar: true - } - requestOptions.url = encodeURI(GOOGLE_SCHOLAR_URL + query) - request(requestOptions, scholarResultsCallback(resolve, reject)) - }) - }) + request(encodeURI(GOOGLE_SCHOLAR_URL + query), scholarResultsCallback(resolve, reject)) }) return p } - function all (query) { - return search(query) - .then(resultsObj => { - // eg n=111 but i have 10 already so 101 remain, - let remainingResultsCount = resultsObj.count - resultsObj.results.length - if (remainingResultsCount > 0) { - // pr = 10 - let pagesRemaining = remainingResultsCount / RESULTS_PER_PAGE - let pageNumbers = [] - for (var i = 1; i <= pagesRemaining + 1; i++) { - pageNumbers.push(i) - } - return Promise.all(pageNumbers.map(i => { - return search(query + '&start=' + i * RESULTS_PER_PAGE) - .then(laterPagesResultsObj => { - return laterPagesResultsObj.results - }) - })) - .then(remainingResultsArr => { - let allResults = resultsObj.results.concat(remainingResultsArr.reduce((a, b) => a.concat(b))) - resultsObj.results = allResults - resultsObj.nextUrl = null - resultsObj.next = null - resultsObj.prevUrl = null - resultsObj.prev = null - return resultsObj - }) - } - }) - } - return { - search: search, - all: all + search: search } })() From 1d5dba91c359f706dbf9c9e06129cc524b61a897 Mon Sep 17 00:00:00 2001 From: sarang87 Date: Wed, 9 Sep 2020 11:24:03 -0400 Subject: [PATCH 2/2] Added 3 index_*.js files to review. index_original.js is the not working code. index_sarang87.js is the working code without the throttle queue and index_throttled_q.js is the working code with the throttled queue which is very slow --- index_original.js | 245 ++++++++++ index.js => index_sarang87.js | 62 ++- index_throttle_q.js | 261 +++++++++++ package-lock.json | 861 ++++++++++++++++++++++++++++++++++ test.js | 8 + 5 files changed, 1423 insertions(+), 14 deletions(-) create mode 100644 index_original.js rename index.js => index_sarang87.js (74%) create mode 100644 index_throttle_q.js create mode 100644 package-lock.json create mode 100644 test.js diff --git a/index_original.js b/index_original.js new file mode 100644 index 0000000..aa25be6 --- /dev/null +++ b/index_original.js @@ -0,0 +1,245 @@ +// Comments by sarang87 - Original index.js from the google-scholar forked repository master branch. +// This works slowly because of the throttled queue and cited count returns bad results. + +'use strict' + +let scholar = (function () { + let request = require('request') + let cheerio = require('cheerio') + let striptags = require('striptags') + const throttledQueue = require('throttled-queue') + + // 1 per 200 ms ~= 5/s per + // https://developers.google.com/webmaster-tools/search-console-api-original/v3/limits + + const perSecThrottle = throttledQueue(5, 1000) + const perMinThrottle = throttledQueue(200, 60 * 1000) + const RESULTS_PER_PAGE = 10 + + const GOOGLE_SCHOLAR_URL = 'https://scholar.google.com/scholar?hl=en&q=' + const GOOGLE_SCHOLAR_URL_PREFIX = 'https://scholar.google.com' + + const ELLIPSIS_HTML_ENTITY = '…' + const ET_AL_NAME = 'et al.' + const CITATION_COUNT_PREFIX = 'Cited by ' + const RELATED_ARTICLES_PREFIX = 'Related articles' + + const STATUS_CODE_FOR_RATE_LIMIT = 503 + const STATUS_MESSAGE_FOR_RATE_LIMIT = 'Service Unavailable' + const STATUS_MESSAGE_BODY = 'This page appears when Google automatically detects requests coming from your computer network which appear to be in violation of the Terms of Service. The block will expire shortly after those requests stop.' + + // regex with thanks to http://stackoverflow.com/a/5917250/1449799 + const RESULT_COUNT_RE = /\W*((\d+|\d{1,3}(,\d{3})*)(\.\d+)?) results/ + + function scholarResultsCallback (resolve, reject) { + return function (error, response, html) { + if (error) { + reject(error) + } else if (response.statusCode !== 200) { + if (response.statusCode === STATUS_CODE_FOR_RATE_LIMIT && response.statusMessage === STATUS_MESSAGE_FOR_RATE_LIMIT && response.body.indexOf(STATUS_MESSAGE_BODY) > -1) { + reject(new Error('you are being rate-limited by google. you have made too many requests too quickly. see: https://support.google.com/websearch/answer/86640')) + } else { + reject(new Error('expected statusCode 200 on http response, but got: ' + response.statusCode)) + } + } else { + let $ = cheerio.load(html) + + let results = $('.gs_r') + let resultCount = 0 + let nextUrl = '' + let prevUrl = '' + if ($('.gs_ico_nav_next').parent().attr('href')) { + nextUrl = GOOGLE_SCHOLAR_URL_PREFIX + $('.gs_ico_nav_next').parent().attr('href') + } + if ($('.gs_ico_nav_previous').parent().attr('href')) { + prevUrl = GOOGLE_SCHOLAR_URL_PREFIX + $('.gs_ico_nav_previous').parent().attr('href') + } + + let processedResults = [] + results.each((i, r) => { + $(r).find('.gs_ri h3 span').remove() + let title = $(r).find('.gs_ri h3').text().trim() + let url = $(r).find('.gs_ri h3 a').attr('href') + let authorNamesHTMLString = $(r).find('.gs_ri .gs_a').html() + let etAl = false + let etAlBegin = false + let authors = [] + let description = $(r).find('.gs_ri .gs_rs').text() + let footerLinks = $(r).find('.gs_ri .gs_fl a') + let citedCount = 0 + let citedUrl = '' + let relatedUrl = '' + let pdfUrl = $($(r).find('.gs_ggsd a')[0]).attr('href') + + if ($(footerLinks[0]).text().indexOf(CITATION_COUNT_PREFIX) >= 0) { + citedCount = $(footerLinks[0]).text().substr(CITATION_COUNT_PREFIX.length) + } + if ($(footerLinks[0]).attr && + $(footerLinks[0]).attr('href') && + $(footerLinks[0]).attr('href').length > 0) { + citedUrl = GOOGLE_SCHOLAR_URL_PREFIX + $(footerLinks[0]).attr('href') + } + if (footerLinks && + footerLinks.length && + footerLinks.length > 0) { + if ($(footerLinks[0]).text && + $(footerLinks[0]).text().indexOf(CITATION_COUNT_PREFIX) >= 0) { + citedCount = $(footerLinks[0]).text().substr(CITATION_COUNT_PREFIX.length) + } + + if ($(footerLinks[1]).text && + $(footerLinks[1]).text().indexOf(RELATED_ARTICLES_PREFIX) >= 0 && + $(footerLinks[1]).attr && + $(footerLinks[1]).attr('href') && + $(footerLinks[1]).attr('href').length > 0) { + relatedUrl = GOOGLE_SCHOLAR_URL_PREFIX + $(footerLinks[1]).attr('href') + } + } + if (authorNamesHTMLString) { + let cleanString = authorNamesHTMLString.substr(0, authorNamesHTMLString.indexOf(' - ')) + if (cleanString.substr(cleanString.length - ELLIPSIS_HTML_ENTITY.length) === ELLIPSIS_HTML_ENTITY) { + etAl = true + cleanString = cleanString.substr(0, cleanString.length - ELLIPSIS_HTML_ENTITY.length) + } + if (cleanString.substr(0, ELLIPSIS_HTML_ENTITY.length) === ELLIPSIS_HTML_ENTITY) { + etAlBegin = true + cleanString = cleanString.substr(ELLIPSIS_HTML_ENTITY.length + 2) + } + let htmlAuthorNames = cleanString.split(', ') + if (etAl) { + htmlAuthorNames.push(ET_AL_NAME) + } + if (etAlBegin) { + htmlAuthorNames.unshift(ET_AL_NAME) + } + authors = htmlAuthorNames.map(name => { + let tmp = cheerio.load(name) + let authorObj = { + name: '', + url: '' + } + if (tmp('a').length === 0) { + authorObj.name = striptags(name) + } else { + authorObj.name = tmp('a').text() + authorObj.url = GOOGLE_SCHOLAR_URL_PREFIX + tmp('a').attr('href') + } + return authorObj + }) + } + + processedResults.push({ + title: title, + url: url, + authors: authors, + description: description, + citedCount: citedCount, + citedUrl: citedUrl, + relatedUrl: relatedUrl, + pdf: pdfUrl + }) + }) + + let resultsCountString = $('#gs_ab_md').text() + if (resultsCountString && resultsCountString.trim().length > 0) { + let matches = RESULT_COUNT_RE.exec(resultsCountString) + if (matches && matches.length > 0) { + resultCount = parseInt(matches[1].replace(/,/g, '')) + } else { + resultCount = processedResults.length + } + } else { + resultCount = processedResults.length + } + + resolve({ + results: processedResults, + count: resultCount, + nextUrl: nextUrl, + prevUrl: prevUrl, + next: function () { + let p = new Promise(function (resolve, reject) { + perMinThrottle(() => { + perSecThrottle(() => { + var requestOptions = { + jar: true + } + requestOptions.url = nextUrl + request(requestOptions, scholarResultsCallback(resolve, reject)) + }) + }) + }) + return p + }, + previous: function () { + let p = new Promise(function (resolve, reject) { + perMinThrottle(() => { + perSecThrottle(() => { + var requestOptions = { + jar: true + } + requestOptions.url = prevUrl + request(requestOptions, scholarResultsCallback(resolve, reject)) + }) + }) + }) + return p + } + }) + } + } + } + + function search (query) { + let p = new Promise(function (resolve, reject) { + perMinThrottle(() => { + perSecThrottle(() => { + var requestOptions = { + jar: true + } + requestOptions.url = encodeURI(GOOGLE_SCHOLAR_URL + query) + request(requestOptions, scholarResultsCallback(resolve, reject)) + }) + }) + }) + return p + } + + function all (query) { + return search(query) + .then(resultsObj => { + // eg n=111 but i have 10 already so 101 remain, + let remainingResultsCount = resultsObj.count - resultsObj.results.length + if (remainingResultsCount > 0) { + // pr = 10 + let pagesRemaining = remainingResultsCount / RESULTS_PER_PAGE + let pageNumbers = [] + for (var i = 1; i <= pagesRemaining + 1; i++) { + pageNumbers.push(i) + } + return Promise.all(pageNumbers.map(i => { + return search(query + '&start=' + i * RESULTS_PER_PAGE) + .then(laterPagesResultsObj => { + return laterPagesResultsObj.results + }) + })) + .then(remainingResultsArr => { + let allResults = resultsObj.results.concat(remainingResultsArr.reduce((a, b) => a.concat(b))) + resultsObj.results = allResults + resultsObj.nextUrl = null + resultsObj.next = null + resultsObj.prevUrl = null + resultsObj.prev = null + return resultsObj + }) + } + }) + } + + return { + search: search, + all: all + } +})() + +module.exports = scholar diff --git a/index.js b/index_sarang87.js similarity index 74% rename from index.js rename to index_sarang87.js index 6bfd8ac..5ae3acc 100644 --- a/index.js +++ b/index_sarang87.js @@ -1,3 +1,4 @@ +// Comments by sarang87 - Works perfectly with changed classes for Cited count and Cited by for scholarResultsCallbackfunction 'use strict' let scholar = (function () { @@ -12,6 +13,7 @@ let scholar = (function () { const ET_AL_NAME = 'et al.' const CITATION_COUNT_PREFIX = 'Cited by ' const RELATED_ARTICLES_PREFIX = 'Related articles' + const RESULTS_PER_PAGE = 10 // regex with thanks to http://stackoverflow.com/a/5917250/1449799 const RESULT_COUNT_RE = /\W*((\d+|\d{1,3}(,\d{3})*)(\.\d+)?) results/ @@ -46,21 +48,20 @@ let scholar = (function () { let etAlBegin = false let authors = [] let description = $(r).find('.gs_rs').text() - console.log("\n" + description + "\n") let footerLinks = $(r).find('.gs_ri .gs_fl') let citedCount = 0 let citedUrl = '' let relatedUrl = '' - // modified code path to extract scholar url from foorerLinks[0] - //console.log($(footerLinks[0]).html() +"\t\t***") + // modified code path to extract scholar url from foorerLinks[0] + //console.log($(footerLinks[0]).html() +"\t\t***") let alist = [] - $(footerLinks[0]).find('a').each(function (index, element) { - alist.push($(element).attr('href')); - }); - //console.log(alist[3] +"\t\txxxx\n\n") - let scholar_url = alist[2] - let related_url = alist[3] + $(footerLinks[0]).find('a').each(function (index, element) { + alist.push($(element).attr('href')); + }); + //console.log(alist[3] +"\t\txxxx\n\n") + let scholar_url = alist[2] + let related_url = alist[3] if ($(footerLinks[0]).text().indexOf(CITATION_COUNT_PREFIX) >= 0) { @@ -70,18 +71,18 @@ let scholar = (function () { //if ($(footerLinks[2]).attr && // $(footerLinks[2]).attr('href') && // $(footerLinks[2]).attr('href').length > 0) { - // console.log( $(footerLinks[2]).attr('href')) + // console.log( $(footerLinks[2]).attr('href')) // citedUrl = GOOGLE_SCHOLAR_URL_PREFIX + $(footerLinks[2]).attr('href') //} citedUrl = GOOGLE_SCHOLAR_URL_PREFIX + scholar_url - relatedUrl = GOOGLE_SCHOLAR_URL_PREFIX + related_url + relatedUrl = GOOGLE_SCHOLAR_URL_PREFIX + related_url if (footerLinks && footerLinks.length && footerLinks.length > 0) { if ($(footerLinks[0]).text && $(footerLinks[0]).text().indexOf(CITATION_COUNT_PREFIX) >= 0) { - //console.log("Length:" + $(footerLinks[0]).text().slice(CITATION_COUNT_PREFIX.length+2,CITATION_COUNT_PREFIX.length+5)) - citedCount = $(footerLinks[0]).text().slice(CITATION_COUNT_PREFIX.length+2,CITATION_COUNT_PREFIX.length+5) + //console.log("Length:" + $(footerLinks[0]).text().split(/(\s+)/)[6]) + citedCount = $(footerLinks[0]).text().split(/(\s+)/)[6] } //if ($(footerLinks[1]).text && @@ -177,8 +178,41 @@ let scholar = (function () { return p } + function all (query) { + return search(query) + .then(resultsObj => { + // eg n=111 but i have 10 already so 101 remain, + let remainingResultsCount = resultsObj.count - resultsObj.results.length + if (remainingResultsCount > 0) { + // pr = 10 + let pagesRemaining = remainingResultsCount / RESULTS_PER_PAGE + let pageNumbers = [] + for (var i = 1; i <= pagesRemaining + 1; i++) { + pageNumbers.push(i) + } + return Promise.all(pageNumbers.map(i => { + return search(query + '&start=' + i * RESULTS_PER_PAGE) + .then(laterPagesResultsObj => { + return laterPagesResultsObj.results + }) + })) + .then(remainingResultsArr => { + let allResults = resultsObj.results.concat(remainingResultsArr.reduce((a, b) => a.concat(b))) + resultsObj.results = allResults + resultsObj.nextUrl = null + resultsObj.next = null + resultsObj.prevUrl = null + resultsObj.prev = null + return resultsObj + }) + } + }) + } + + return { - search: search + search: search, + all: all } })() diff --git a/index_throttle_q.js b/index_throttle_q.js new file mode 100644 index 0000000..17a8904 --- /dev/null +++ b/index_throttle_q.js @@ -0,0 +1,261 @@ + +// Comments by sarang87 - Works perfectly with changed classes for Cited count and Cited by for +// scholarResultsCallbackfunction using throttled queue with much slower performance for returning results + + 'use strict' + + let scholar = (function () { + let request = require('request') + let cheerio = require('cheerio') + let striptags = require('striptags') + const throttledQueue = require('throttled-queue') + + // 1 per 200 ms ~= 5/s per + // https://developers.google.com/webmaster-tools/search-console-api-original/v3/limits + + const perSecThrottle = throttledQueue(5, 1000) + const perMinThrottle = throttledQueue(200, 60 * 1000) + const RESULTS_PER_PAGE = 10 + + const GOOGLE_SCHOLAR_URL = 'https://scholar.google.com/scholar?hl=en&q=' + const GOOGLE_SCHOLAR_URL_PREFIX = 'https://scholar.google.com' + + const ELLIPSIS_HTML_ENTITY = '…' + const ET_AL_NAME = 'et al.' + const CITATION_COUNT_PREFIX = 'Cited by ' + const RELATED_ARTICLES_PREFIX = 'Related articles' + + const STATUS_CODE_FOR_RATE_LIMIT = 503 + const STATUS_MESSAGE_FOR_RATE_LIMIT = 'Service Unavailable' + const STATUS_MESSAGE_BODY = 'This page appears when Google automatically detects requests coming from your computer network which appear to be in violation of the Terms of Service. The block will expire shortly after those requests stop.' + + // regex with thanks to http://stackoverflow.com/a/5917250/1449799 + const RESULT_COUNT_RE = /\W*((\d+|\d{1,3}(,\d{3})*)(\.\d+)?) results/ + + function scholarResultsCallback (resolve, reject) { + return function (error, response, html) { + if (error) { + reject(error) + } else if (response.statusCode !== 200) { + if (response.statusCode === STATUS_CODE_FOR_RATE_LIMIT && response.statusMessage === STATUS_MESSAGE_FOR_RATE_LIMIT && response.body.indexOf(STATUS_MESSAGE_BODY) > -1) { + reject(new Error('you are being rate-limited by google. you have made too many requests too quickly. see: https://support.google.com/websearch/answer/86640')) + } else { + reject(new Error('expected statusCode 200 on http response, but got: ' + response.statusCode)) + } + } else { + let $ = cheerio.load(html) + + let results = $('.gs_ri') + let resultCount = 0 + let nextUrl = '' + let prevUrl = '' + if ($('.gs_ico_nav_next').parent().attr('href')) { + nextUrl = GOOGLE_SCHOLAR_URL_PREFIX + $('.gs_ico_nav_next').parent().attr('href') + } + if ($('.gs_ico_nav_previous').parent().attr('href')) { + prevUrl = GOOGLE_SCHOLAR_URL_PREFIX + $('.gs_ico_nav_previous').parent().attr('href') + } + + let processedResults = [] + results.each((i, r) => { + $(r).find('h3 span').remove() + let title = $(r).find('h3').text().trim() + let url = $(r).find('h3 a').attr('href') + let authorNamesHTMLString = $(r).find('.gs_a').html() + let etAl = false + let etAlBegin = false + let authors = [] + let description = $(r).find('.gs_rs').text() + console.log("\n" + description + "\n") + let footerLinks = $(r).find('.gs_ri .gs_fl') + let citedCount = 0 + let citedUrl = '' + let relatedUrl = '' + + // modified code path to extract scholar url from foorerLinks[0] + //console.log($(footerLinks[0]).html() +"\t\t***") + let alist = [] + $(footerLinks[0]).find('a').each(function (index, element) { + alist.push($(element).attr('href')); + }); + //console.log(alist[3] +"\t\txxxx\n\n") + let scholar_url = alist[2] + let related_url = alist[3] + + + if ($(footerLinks[0]).text().indexOf(CITATION_COUNT_PREFIX) >= 0) { + + citedCount = $(footerLinks[0]).text().substr(CITATION_COUNT_PREFIX.length) + } + // f ($(footerLinks[2]).attr && + // $(footerLinks[2]).attr('href') && + // $(footerLinks[2]).attr('href').length > 0) { + // console.log( $(footerLinks[2]).attr('href')) + // citedUrl = GOOGLE_SCHOLAR_URL_PREFIX + $(footerLinks[2]).attr('href') + //} + citedUrl = GOOGLE_SCHOLAR_URL_PREFIX + scholar_url + relatedUrl = GOOGLE_SCHOLAR_URL_PREFIX + related_url + if (footerLinks && + footerLinks.length && + footerLinks.length > 0) { + if ($(footerLinks[0]).text && + $(footerLinks[0]).text().indexOf(CITATION_COUNT_PREFIX) >= 0) { + //console.log("Length:" + $(footerLinks[0]).text().slice(CITATION_COUNT_PREFIX.length+2,CITATION_COUNT_PREFIX.length+5)) + citedCount = $(footerLinks[0]).text().split(/(\s+)/)[6] + } + + //if ($(footerLinks[1]).text && + // $(footerLinks[1]).text().indexOf(RELATED_ARTICLES_PREFIX) >= 0 && + // $(footerLinks[1]).attr && + // $(footerLinks[1]).attr('href') && + // $(footerLinks[1]).attr('href').length > 0) { + // relatedUrl = GOOGLE_SCHOLAR_URL_PREFIX + $(footerLinks[1]).attr('href') + // } + } + if (authorNamesHTMLString) { + let cleanString = authorNamesHTMLString.substr(0, authorNamesHTMLString.indexOf(' - ')) + if (cleanString.substr(cleanString.length - ELLIPSIS_HTML_ENTITY.length) === ELLIPSIS_HTML_ENTITY) { + etAl = true + cleanString = cleanString.substr(0, cleanString.length - ELLIPSIS_HTML_ENTITY.length) + } + if (cleanString.substr(0, ELLIPSIS_HTML_ENTITY.length) === ELLIPSIS_HTML_ENTITY) { + etAlBegin = true + cleanString = cleanString.substr(ELLIPSIS_HTML_ENTITY.length + 2) + } + let htmlAuthorNames = cleanString.split(', ') + if (etAl) { + htmlAuthorNames.push(ET_AL_NAME) + } + if (etAlBegin) { + htmlAuthorNames.unshift(ET_AL_NAME) + } + authors = htmlAuthorNames.map(name => { + let tmp = cheerio.load(name) + let authorObj = { + name: '', + url: '' + } + if (tmp('a').length === 0) { + authorObj.name = striptags(name) + } else { + authorObj.name = tmp('a').text() + authorObj.url = GOOGLE_SCHOLAR_URL_PREFIX + tmp('a').attr('href') + } + return authorObj + }) + } + + processedResults.push({ + title: title, + url: url, + authors: authors, + description: description, + citedCount: citedCount, + citedUrl: citedUrl, + relatedUrl: relatedUrl + }) + }) + + let resultsCountString = $('#gs_ab_md').text() + if (resultsCountString && resultsCountString.trim().length > 0) { + let matches = RESULT_COUNT_RE.exec(resultsCountString) + if (matches && matches.length > 0) { + resultCount = parseInt(matches[1].replace(/,/g, '')) + } else { + resultCount = processedResults.length + } + } else { + resultCount = processedResults.length + } + + resolve({ + results: processedResults, + count: resultCount, + nextUrl: nextUrl, + prevUrl: prevUrl, + next: function () { + let p = new Promise(function (resolve, reject) { + perMinThrottle(() => { + perSecThrottle(() => { + var requestOptions = { + jar: true + } + requestOptions.url = nextUrl + request(requestOptions, scholarResultsCallback(resolve, reject)) + }) + }) + }) + return p + }, + previous: function () { + let p = new Promise(function (resolve, reject) { + perMinThrottle(() => { + perSecThrottle(() => { + var requestOptions = { + jar: true + } + requestOptions.url = prevUrl + request(requestOptions, scholarResultsCallback(resolve, reject)) + }) + }) + }) + return p + } + }) + } + } + } + + function search (query) { + let p = new Promise(function (resolve, reject) { + perMinThrottle(() => { + perSecThrottle(() => { + var requestOptions = { + jar: true + } + requestOptions.url = encodeURI(GOOGLE_SCHOLAR_URL + query) + request(requestOptions, scholarResultsCallback(resolve, reject)) + }) + }) + }) + return p + } + + function all (query) { + return search(query) + .then(resultsObj => { + // eg n=111 but i have 10 already so 101 remain, + let remainingResultsCount = resultsObj.count - resultsObj.results.length + if (remainingResultsCount > 0) { + // pr = 10 + let pagesRemaining = remainingResultsCount / RESULTS_PER_PAGE + let pageNumbers = [] + for (var i = 1; i <= pagesRemaining + 1; i++) { + pageNumbers.push(i) + } + return Promise.all(pageNumbers.map(i => { + return search(query + '&start=' + i * RESULTS_PER_PAGE) + .then(laterPagesResultsObj => { + return laterPagesResultsObj.results + }) + })) + .then(remainingResultsArr => { + let allResults = resultsObj.results.concat(remainingResultsArr.reduce((a, b) => a.concat(b))) + resultsObj.results = allResults + resultsObj.nextUrl = null + resultsObj.next = null + resultsObj.prevUrl = null + resultsObj.prev = null + return resultsObj + }) + } + }) + } + + return { + search: search, + all: all + } +})() + +module.exports = scholar diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..0618807 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,861 @@ +{ + "name": "google-scholar", + "version": "1.5.1", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "abab": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/abab/-/abab-1.0.4.tgz", + "integrity": "sha1-X6rZwsB/YN12dw9xzwJbYqY8/U4=", + "optional": true + }, + "acorn": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-2.7.0.tgz", + "integrity": "sha1-q259nYhqrKiwhbwzEreaGYQz8Oc=", + "optional": true + }, + "acorn-globals": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-1.0.9.tgz", + "integrity": "sha1-VbtemGkVB7dFedBRNBMhfDgMVM8=", + "optional": true, + "requires": { + "acorn": "^2.1.0" + } + }, + "ajv": { + "version": "6.12.4", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.4.tgz", + "integrity": "sha512-eienB2c9qVQs2KWexhkrdMLVDoIQCz5KSeLxwg9Lzk4DOfBtIK9PQwwufcsn1jjGuf9WZmqPMbGxOzfcuphJCQ==", + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "asn1": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "requires": { + "safer-buffer": "~2.1.0" + } + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + }, + "assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" + }, + "aws4": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.10.1.tgz", + "integrity": "sha512-zg7Hz2k5lI8kb7U32998pRRFin7zJlkfezGJjUc2heaD4Pw2wObakCDVzkKztTm/Ln7eiVvYsjqak0Ed4LkMDA==" + }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "requires": { + "tweetnacl": "^0.14.3" + } + }, + "boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=" + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" + }, + "chai": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-3.5.0.tgz", + "integrity": "sha1-TQJjewZ/6Vi9v906QOxW/vc3Mkc=", + "dev": true, + "requires": { + "assertion-error": "^1.0.1", + "deep-eql": "^0.1.3", + "type-detect": "^1.0.0" + } + }, + "cheerio": { + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-0.20.0.tgz", + "integrity": "sha1-XHEPK6uVZTJyhCugHG6mGzVF7DU=", + "requires": { + "css-select": "~1.2.0", + "dom-serializer": "~0.1.0", + "entities": "~1.1.1", + "htmlparser2": "~3.8.1", + "jsdom": "^7.0.2", + "lodash": "^4.1.0" + } + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "commander": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.3.0.tgz", + "integrity": "sha1-/UMOiJgy7DU7ms0d4hfBHLPu+HM=", + "dev": true + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "css-select": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", + "integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=", + "requires": { + "boolbase": "~1.0.0", + "css-what": "2.1", + "domutils": "1.5.1", + "nth-check": "~1.0.1" + } + }, + "css-what": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.3.tgz", + "integrity": "sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg==" + }, + "cssom": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", + "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", + "optional": true + }, + "cssstyle": { + "version": "0.2.37", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-0.2.37.tgz", + "integrity": "sha1-VBCXI0yyUTyDzu06zdwn/yeYfVQ=", + "optional": true, + "requires": { + "cssom": "0.3.x" + } + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "requires": { + "assert-plus": "^1.0.0" + } + }, + "debug": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", + "dev": true, + "requires": { + "ms": "0.7.1" + } + }, + "deep-eql": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-0.1.3.tgz", + "integrity": "sha1-71WKyrjeJSBs1xOQbXTlaTDrafI=", + "dev": true, + "requires": { + "type-detect": "0.1.1" + }, + "dependencies": { + "type-detect": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-0.1.1.tgz", + "integrity": "sha1-C6XsKohWQORw6k6FBZcZANrFiCI=", + "dev": true + } + } + }, + "deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "optional": true + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + }, + "diff": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-1.4.0.tgz", + "integrity": "sha1-fyjS657nsVqX79ic5j3P2qPMur8=", + "dev": true + }, + "dom-serializer": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.1.tgz", + "integrity": "sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA==", + "requires": { + "domelementtype": "^1.3.0", + "entities": "^1.1.1" + } + }, + "domelementtype": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", + "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==" + }, + "domhandler": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.3.0.tgz", + "integrity": "sha1-LeWaCCLVAn+r/28DLCsloqir5zg=", + "requires": { + "domelementtype": "1" + } + }, + "domutils": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", + "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", + "requires": { + "dom-serializer": "0", + "domelementtype": "1" + } + }, + "ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "requires": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "entities": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", + "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==" + }, + "escape-string-regexp": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.2.tgz", + "integrity": "sha1-Tbwv5nTnGUnK8/smlc5/LcHZqNE=", + "dev": true + }, + "escodegen": { + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz", + "integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==", + "optional": true, + "requires": { + "esprima": "^4.0.1", + "estraverse": "^4.2.0", + "esutils": "^2.0.2", + "optionator": "^0.8.1", + "source-map": "~0.6.1" + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "optional": true + }, + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "optional": true + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "optional": true + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "optional": true + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" + }, + "form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "requires": { + "assert-plus": "^1.0.0" + } + }, + "glob": { + "version": "3.2.11", + "resolved": "https://registry.npmjs.org/glob/-/glob-3.2.11.tgz", + "integrity": "sha1-Spc/Y1uRkPcV0QmH1cAP0oFevj0=", + "dev": true, + "requires": { + "inherits": "2", + "minimatch": "0.3" + } + }, + "growl": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.9.2.tgz", + "integrity": "sha1-Dqd0NxXbjY3ixe3hd14bRayFwC8=", + "dev": true + }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" + }, + "har-validator": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", + "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", + "requires": { + "ajv": "^6.12.3", + "har-schema": "^2.0.0" + } + }, + "htmlparser2": { + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.8.3.tgz", + "integrity": "sha1-mWwosZFRaovoZQGn15dX5ccMEGg=", + "requires": { + "domelementtype": "1", + "domhandler": "2.3", + "domutils": "1.5", + "entities": "1.0", + "readable-stream": "1.1" + }, + "dependencies": { + "entities": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.0.0.tgz", + "integrity": "sha1-sph6o4ITR/zeZCsk/fyeT7cSvyY=" + } + } + }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" + }, + "jade": { + "version": "0.26.3", + "resolved": "https://registry.npmjs.org/jade/-/jade-0.26.3.tgz", + "integrity": "sha1-jxDXl32NefL2/4YqgbBRPMslaGw=", + "dev": true, + "requires": { + "commander": "0.6.1", + "mkdirp": "0.3.0" + }, + "dependencies": { + "commander": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-0.6.1.tgz", + "integrity": "sha1-+mihT2qUXVTbvlDYzbMyDp47GgY=", + "dev": true + }, + "mkdirp": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.0.tgz", + "integrity": "sha1-G79asbqCevI1dRQ0kEJkVfSB/h4=", + "dev": true + } + } + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" + }, + "jsdom": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-7.2.2.tgz", + "integrity": "sha1-QLQCdwwr2iNGkJa+6Rq2deOx/G4=", + "optional": true, + "requires": { + "abab": "^1.0.0", + "acorn": "^2.4.0", + "acorn-globals": "^1.0.4", + "cssom": ">= 0.3.0 < 0.4.0", + "cssstyle": ">= 0.2.29 < 0.3.0", + "escodegen": "^1.6.1", + "nwmatcher": ">= 1.3.7 < 2.0.0", + "parse5": "^1.5.1", + "request": "^2.55.0", + "sax": "^1.1.4", + "symbol-tree": ">= 3.1.0 < 4.0.0", + "tough-cookie": "^2.2.0", + "webidl-conversions": "^2.0.0", + "whatwg-url-compat": "~0.6.5", + "xml-name-validator": ">= 2.0.1 < 3.0.0" + } + }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" + }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "optional": true, + "requires": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + } + }, + "lodash": { + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==" + }, + "lru-cache": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz", + "integrity": "sha1-bUUk6LlV+V1PW1iFHOId1y+06VI=", + "dev": true + }, + "mime-db": { + "version": "1.44.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", + "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==" + }, + "mime-types": { + "version": "2.1.27", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", + "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", + "requires": { + "mime-db": "1.44.0" + } + }, + "minimatch": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.3.0.tgz", + "integrity": "sha1-J12O2qxPG7MyZHIInnlJyDlGmd0=", + "dev": true, + "requires": { + "lru-cache": "2", + "sigmund": "~1.0.0" + } + }, + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, + "requires": { + "minimist": "0.0.8" + } + }, + "mocha": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-2.5.3.tgz", + "integrity": "sha1-FhvlvetJZ3HrmzV0UFC2IrWu/Fg=", + "dev": true, + "requires": { + "commander": "2.3.0", + "debug": "2.2.0", + "diff": "1.4.0", + "escape-string-regexp": "1.0.2", + "glob": "3.2.11", + "growl": "1.9.2", + "jade": "0.26.3", + "mkdirp": "0.5.1", + "supports-color": "1.2.0", + "to-iso-string": "0.0.2" + } + }, + "ms": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", + "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=", + "dev": true + }, + "nth-check": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", + "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", + "requires": { + "boolbase": "~1.0.0" + } + }, + "nwmatcher": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/nwmatcher/-/nwmatcher-1.4.4.tgz", + "integrity": "sha512-3iuY4N5dhgMpCUrOVnuAdGrgxVqV2cJpM+XNccjR2DKOB1RUP0aA+wGXEiNziG/UKboFyGBIoKOaNlJxx8bciQ==", + "optional": true + }, + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" + }, + "optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "optional": true, + "requires": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + } + }, + "parse5": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-1.5.1.tgz", + "integrity": "sha1-m387DeMr543CQBsXVzzK8Pb1nZQ=", + "optional": true + }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" + }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "optional": true + }, + "psl": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", + "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==" + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" + }, + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" + }, + "readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "request": { + "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", + "optional": true + }, + "sigmund": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", + "integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "optional": true + }, + "sshpk": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", + "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", + "requires": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" + }, + "striptags": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/striptags/-/striptags-2.2.1.tgz", + "integrity": "sha1-TEULcI1BuL85zyTEn/I0/Gqr/TI=" + }, + "supports-color": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-1.2.0.tgz", + "integrity": "sha1-/x7R5hFp0Gs88tWI4YixjYhH4X4=", + "dev": true + }, + "symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "optional": true + }, + "throttled-queue": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/throttled-queue/-/throttled-queue-1.0.7.tgz", + "integrity": "sha512-/HT49S7m+NvdyJMoMRzIYlawKjeHn8jEc8TZaGmFi5IBu09hIiU/QoP1zcrB9X2qsVC11PgfRfqd8zEQs7PlEA==" + }, + "to-iso-string": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/to-iso-string/-/to-iso-string-0.0.2.tgz", + "integrity": "sha1-TcGeZk38y+Jb2NtQiwDG2hWCVdE=", + "dev": true + }, + "tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "requires": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + } + }, + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=", + "optional": true + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" + }, + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "optional": true, + "requires": { + "prelude-ls": "~1.1.2" + } + }, + "type-detect": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-1.0.0.tgz", + "integrity": "sha1-diIXzAbbJY7EiQihKY6LlRIejqI=", + "dev": true + }, + "uri-js": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.0.tgz", + "integrity": "sha512-B0yRTzYdUCCn9n+F4+Gh4yIDtMQcaJsmYBDsTSG8g/OejKBodLQ2IHfN3bM7jUsRXndopT7OIXWdYqc1fjmV6g==", + "requires": { + "punycode": "^2.1.0" + } + }, + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "webidl-conversions": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-2.0.1.tgz", + "integrity": "sha1-O/glj30xjHRDw28uFpQCoaZwNQY=", + "optional": true + }, + "whatwg-url-compat": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/whatwg-url-compat/-/whatwg-url-compat-0.6.5.tgz", + "integrity": "sha1-AImBEa9om7CXVBzVpFymyHmERb8=", + "optional": true, + "requires": { + "tr46": "~0.0.1" + } + }, + "word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "optional": true + }, + "xml-name-validator": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-2.0.1.tgz", + "integrity": "sha1-TYuPHszTQZqjYgYb7O9RXh5VljU=", + "optional": true + } + } +} diff --git a/test.js b/test.js new file mode 100644 index 0000000..8da09fa --- /dev/null +++ b/test.js @@ -0,0 +1,8 @@ +'use strict' + +let scholar = require('./index_sarang87.js') + +scholar.all('amazon redshift') + .then(resultsObj => { + console.log(resultsObj) + })