@trademe/reviewme
Version:
Google Play and App Store reviews posted to Slack
214 lines (166 loc) • 7.4 kB
JavaScript
const controller = require('./reviews');
var {google} = require('googleapis');
var playScraper = require('google-play-scraper');
var androidVersions = require('android-versions')
exports.startReview = function (config, first_run) {
var appInformation = {};
//scrape Google Play for app information first
playScraper.app({appId: config.appId})
.then(function (appData, error) {
if (error) {
return console.error("ERROR: [" + config.appId + "] Could not scrape Google Play, " + error);
}
appInformation.appName = appData.title;
appInformation.appIcon = appData.icon;
exports.fetchGooglePlayReviews(config, appInformation, function (reviews) {
// If we don't have any published reviews, then treat this as a baseline fetch, we won't post any
// reviews to slack, but new ones from now will be posted
if (first_run) {
var reviewLength = reviews.length;
for (var i = 0; i < reviewLength; i++) {
var initialReview = reviews[i];
controller.markReviewAsPublished(config, initialReview);
}
if (config.dryRun && reviews.length > 0) {
// Force publish a review if we're doing a dry run
publishReview(appInformation, config, reviews[reviews.length - 1], config.dryRun);
}
}
else {
exports.handleFetchedGooglePlayReviews(config, appInformation, reviews);
}
var interval_seconds = config.interval ? config.interval : DEFAULT_INTERVAL_SECONDS;
setInterval(function (config, appInformation) {
if (config.verbose) console.log("INFO: [" + config.appId + "] Fetching Google Play reviews");
exports.fetchGooglePlayReviews(config, appInformation, function (reviews) {
exports.handleFetchedGooglePlayReviews(config, appInformation, reviews);
});
}, interval_seconds * 1000, config, appInformation);
});
});
};
function publishReview(appInformation, config, review, force) {
if (!controller.reviewPublished(config, review) || force) {
if (config.verbose) console.log("INFO: Received new review: " + JSON.stringify(review));
var message = slackMessage(review, config, appInformation);
controller.postToSlack(message, config);
controller.markReviewAsPublished(config, review);
} else {
if (config.verbose) console.log("INFO: Review already published: " + review.text);
}
}
exports.handleFetchedGooglePlayReviews = function (config, appInformation, reviews) {
if (config.verbose) console.log("INFO: [" + config.appId + "] Handling fetched reviews");
for (var n = 0; n < reviews.length; n++) {
var review = reviews[n];
publishReview(appInformation, config, review, false)
}
};
exports.fetchGooglePlayReviews = function (config, appInformation, callback) {
if (config.verbose) console.log("INFO: Fetching Google Play reviews for " + config.appId);
const scopes = ['https://www.googleapis.com/auth/androidpublisher'];
//read publisher json key
var publisherJson;
if (typeof config.publisherKey === 'object') {
publisherJson = config.publisherKey;
} else {
try {
publisherJson = JSON.parse(require('fs').readFileSync(config.publisherKey, 'utf8'));
} catch (e) {
console.warn(e)
}
}
var jwt;
try {
jwt = new google.auth.JWT(publisherJson.client_id, null, publisherJson.private_key, scopes, null);
} catch (e) {
console.warn(e)
}
jwt.authorize(function (err, tokens) {
if (err) {
return console.log(err)
}
//get list of reviews using Google Play Publishing API
google.androidpublisher('v3').reviews.list({
auth: jwt,
packageName: config.appId
}, function (err, resp) {
if (err) {
return console.error("ERROR: [" + config.appId + "] Could not fetch Google Play reviews, " + err);
}
if (config.verbose) console.log("INFO: [" + config.appId + "] Received reviews from Google Play");
if (!resp.data.reviews) {
callback([]);
return;
}
var reviews = resp.data.reviews.map(function (review) {
var comment = review.comments[0].userComment;
var out = {};
out.id = review.reviewId;
out.author = review.authorName;
out.version = comment.appVersionName;
out.versionCode = comment.appVersionCode;
out.osVersion = comment.androidOsVersion;
if (comment.deviceMetadata) {
out.device = comment.deviceMetadata.productName;
}
out.text = comment.text;
out.rating = comment.starRating;
out.link = 'https://play.google.com/store/apps/details?id=' + config.appId + '&reviewId=' + review.reviewId;
out.storeName = "Google Play";
return out;
});
callback(reviews);
})
});
};
var slackMessage = function (review, config, appInformation) {
if (config.verbose) console.log("INFO: Creating message for review " + review.title);
var stars = "";
for (var i = 0; i < 5; i++) {
stars += i < review.rating ? "★" : "☆";
}
var color = review.rating >= 4 ? "good" : (review.rating >= 2 ? "warning" : "danger");
var text = "";
text += review.text + "\n";
var footer = "";
if (review.version) {
footer += " for v" + review.version + ' (' + review.versionCode + ') ';
}
if (review.osVersion) {
footer += ' Android ' + getVersionNameForCode(review.osVersion)
}
if (review.device) {
footer += ', ' + review.device
}
if (review.link) {
footer += " - " + "<" + review.link + "|" + appInformation.appName + ", " + review.storeName + ">";
} else {
footer += " - " + appInformation.appName + ", " + review.storeName;
}
var title = stars;
if (review.title) {
title = title + " – " + review.title;
}
return {
"channel": config.channel,
"attachments": [
{
"mrkdwn_in": ["text", "pretext", "title", "footer"],
"color": color,
"author_name": review.author,
"thumb_url": config.showAppIcon ? appInformation.appIcon : config.botIcon,
"title": title,
"text": text,
"footer": footer
}
]
};
};
var getVersionNameForCode = function (versionCode) {
var version = androidVersions.get(versionCode);
if (version != null) {
return version.semver;
}
return "";
};