exports.getTrends = function (profileId, username, password, jsonOut) { // local vars var dateFormat = 'YYYY-MM-DD'; var dateToday = moment().format(dateFormat); var dateOneMonthAgo = moment(dateToday).subtract(28, 'days').format(dateFormat); var dateSixMonthsAgo = moment(dateToday).subtract(183, 'days').format(dateFormat); var dateSevenMonthsAgo = moment(dateSixMonthsAgo).subtract(28, 'days').format(dateFormat); var dateYearAgo = moment(dateToday).subtract(365, 'days').format(dateFormat); var dateYearAndMonthAgo = moment(dateYearAgo).subtract(28, 'days').format(dateFormat); var gaRequestOpts = { lastMonthVisits: getGaRequestOpts(profileId, dateOneMonthAgo, dateToday, 'ga:visits', ''), lastMonthBrowsers: getGaRequestOpts(profileId, dateOneMonthAgo, dateToday, 'ga:visits', 'ga:browser,ga:browserVersion'), sixMonthsAgoVisits: getGaRequestOpts(profileId, dateSevenMonthsAgo, dateSixMonthsAgo, 'ga:visits', ''), sixMonthsAgoBrowsers: getGaRequestOpts(profileId, dateSevenMonthsAgo, dateSixMonthsAgo, 'ga:visits', 'ga:browser,ga:browserVersion'), yearAgoVisits: getGaRequestOpts(profileId, dateYearAndMonthAgo, dateYearAgo, 'ga:visits', ''), yearAgoBrowsers: getGaRequestOpts(profileId, dateYearAndMonthAgo, dateYearAgo, 'ga:visits', 'ga:browser,ga:browserVersion') }; var errMsgSent = false; var progress = new Progger(); var ga; // @todo: this sucks // I think the only uncaught is with GA logins failing (ie. credentials are incorrect) process.on('uncaughtException', function (err) { // stop progress ticker if it's running if (typeof progress === 'object' && progress.isRunning()) { progress.stop(); } if (err.message) { return console.error('\n' + err.message.red); } return console.error('\nAn unknown error has occurred!'.red); }); // returns a promise for a GA request function gaRequest(description) { var d = Q.defer(); ga.get(gaRequestOpts[description], function (err, entries) { // reject promise or resolve with entries if (err) { // this is a hacky way of preventing error message displaying multiple times if (err.message === 'User does not have sufficient permissions for this profile.') { if (!errMsgSent) { progress.stop(); errMsgSent = true; console.log('\n' + 'This GA user does not have permissions to read profile: '.red + profileId); } d.reject(''); } else { d.reject(err); } } else { d.resolve({description: description, entries: entries}); } }); return d.promise; } // start timer console.time('time-to-run'); if (!jsonOut) { process.stdout.write('\nDownloading data from Google Analytics'); } // start progress ticker progress.start(); // create new google analytics object ga = new GA.GA({'user': username, 'password': password}); // start GA session ga.login(function (err, token) { if (err) { return console.log('error logging in to Google Analytics'.error); } // complete a queue of promise-returning functions Q.allSettled([ gaRequest('lastMonthVisits'), gaRequest('lastMonthBrowsers'), gaRequest('sixMonthsAgoVisits'), gaRequest('sixMonthsAgoBrowsers'), gaRequest('yearAgoVisits'), gaRequest('yearAgoBrowsers') ]) .then(function (results) { var passed = []; results.forEach(function (result) { if (result.state === "fulfilled") { passed.push(result.value); } else { console.error(result.reason); } }); return processResults(passed); }) .then(function (processed) { if (jsonOut) { process.stdout.write('\n\n' + JSON.stringify(processed, null, ' ')); } // get table for printing to the command line return getCliTable(processed); }) .then(function (table) { // stop progress ticker progress.stop(); console.log('\n'); // if there were errors getting the data don't output empty table if (table.length <= 1) { return console.error('No data available!'.red); } if (!jsonOut) { console.log(table.toString()); } }) .fail(function (error) { // stop progress ticker progress.stop(); // if any errors happened in the promise-chain, output here console.log('\n'); console.error('fail: ', error.stack); }) .finally(function () { // end timer if (!jsonOut) { console.log('\n'); console.timeEnd('time-to-run'); } }); }); };
module.exports = function (userUrl, pretty) { var requestOpts, progress; requestOpts = { uri: userUrl, headers: { 'User-Agent': userAgentMap['ie'] } }; // progress ticker progress = new Progger(); process.stdout.write('\nRetrieving CSS data from ' + userUrl); // start progress ticker progress.start(); // request url and look for linked stylesheets and inline styles request(requestOpts, function (error, response, body) { var $, inlinestyles, inlinestylesTotal, stylesheets, stylesheetTotal, fakeRequest, stylesheetList = [], requests = []; if (error) { // stop progress ticker progress.stop(); return console.error(error); } if (response.statusCode !== 200) { // stop progress ticker progress.stop(); return console.error('Error connecting to ' + userUrl + '. Status code: ' + response.statusCode); } // load response body into variable and use jquery to parse it for styles $ = cheerio.load(body), stylesheets = $('link[rel="stylesheet"]'), stylesheetTotal = stylesheets.length, inlinestyles = $('style'), inlinestylesTotal = inlinestyles.length; // check inline styles, then check linked stylesheets, then parse, then output async.waterfall([ // if there are inline styles, 'fake' a request object // there's a better way to do this... function (callback) { if (inlinestylesTotal > 0) { for (var i = 0; i < inlinestylesTotal; i++) { fakeRequest = { request: {} }; // super kludge fakeRequest.cssType = 'inline'; fakeRequest.request.href = 'inline-style-block-' + (i + 1); fakeRequest.body = inlinestyles[i].children[0] ? inlinestyles[i].children[0].data : ''; requests.push(fakeRequest); } callback(null, requests); } else { callback(null, requests); } }, // if there are linked stylesheets populate an array with their urls function (requests, callback) { if (stylesheetTotal > 0) { for (var i = 0; i < stylesheetTotal; i++) { stylesheetList.push( utils.normalizeUrl( stylesheets[i].attribs.href, userUrl ) ); } // get linked stylesheets async.map(stylesheetList, utils.getUrl, function (error, results) { if (error) { // stop progress ticker progress.stop(); console.error('error requesting stylesheets'); } else { for (var j = 0, len = results.length; j < len; j++) { requests.push(results[j]); } callback(null, requests); } }); } else { callback(null, requests); } }, // pipe each style request into parseCss and pass along results function (requests, callback) { async.map(requests, parseCss, function (error, results) { if (error) { // stop progress ticker progress.stop(); console.error('error parsing styles'); } else { callback(null, results); } }); }, // add a properties to track the original url function (results, callback) { var result = { page: userUrl, result: results }; // return json callback(null, result); } ], // all done function (error, result) { // stop progress ticker progress.stop(); if (error) { return console.error(error); } if (pretty) { // pretty print results prettyPrint(result); } else { // convert to json console.log(JSON.stringify(result, null, 2)); } }); }); };