/*=====================================================================================*\
| The Amazon Review Tabulator - TART |
| (c) 2016 by Another Floyd |
| From your "Public Reviews Written by You" page on Amazon, this script collects and |
| tabulates vote tallies and related information, from all of your Amazon reviews. |
\*=====================================================================================*/
// ==UserScript==
// @name The Amazon Review Tabulator - TART
// @namespace floyd.scripts
// @version 1.0.1
// @author Another Floyd at Amazon.com
// @description Lists all of your reviews with vote and comment tallies
// @include https://www.amazon.com/gp/cdp/member-reviews/*
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_xmlhttpRequest
// @grant GM_log
// ==/UserScript==
// Start
(function() {
var userId = "";
var reviewCount = 0;
var reviewerRanking = "";
var helpfulVotes = 0;
var urlStart = "";
var urlEnd = "";
var displayBuffer = "";
var oldStoreItemIDs = [];
var oldStoreUpvotes = [];
var oldStoreDownvotes = [];
var oldStoreComments = [];
// if I ever get this to open in a new window, instead of overwriting the Amazon
// window, items below should be cleared at start of tabulate(), so that repeated runs
// will work without error
var newStoreItemIDs = "";
var newStoreUpvotes = "";
var newStoreDownvotes = "";
var newStoreComments = "";
var tallyUpvotes = 0;
var tallyDownvotes = 0;
var tallyStars = 0;
var tallyComments = 0;
function tabulate() {
// set up top of display page
displayBuffer += "<style type='text/css'> \
.tg {border-collapse:collapse;border-spacing:0} \
.tg td{font-family:Arial, sans-serif;font-size:12px;padding:10px 5px;border-style:solid;border-width:1px;overflow:hidden;word-break:normal} \
.tg th{font-family:Arial, sans-serif;font-size:12px;padding:10px 5px;border-style:solid;border-width:1px;overflow:hidden;word-break:normal} \
.tg .header-left{font-weight:bold;background-color:#010066;color:#ffffff;text-align:left} \
.tg .header-right{font-weight:bold;background-color:#010066;color:#ffffff;text-align:right} \
.tg .cell-left{text-align:left} \
.tg .cell-right{text-align:right} \
.tg .hilite-left{text-align:left;background-color:#FFFF12} \
.tg .hilite-right{text-align:right;background-color:#FFFF12} \
</style> \
<span style='font-size:16px;font-weight:bold'>Amazon Review Details</span><br> \
<span style='font-size:8px'>Prepared with The Amazon Review Tabulator - TART</span> \
<p>Top Reviewer Ranking: " + reviewerRanking + "<br> \
Reviews Available: " + reviewCount + "<br> \
Helpful Votes: " + helpfulVotes + "<br> \
Upvote/Review Ratio: " + (helpfulVotes/reviewCount).toFixed(2) + "<br></p> \
<table class='tg'> \
<tr><th class='header-left'>#</th><th class='header-left'>Item</th><th class='header-left'>Date</th><th class='header-right'>Stars<br></th><th class='header-right'>Upvotes</th><th class='header-right'>Downvotes</th><th class='header-right'>% Helpful<br></th><th class='header-right'>Comments</th></tr>";
// read in stored info from past run, for use in change detection
oldStoreItemIDs = GM_getValue("recentItemIDs", "").split(" ");
oldStoreUpvotes = GM_getValue("recentUpvotes", "").split(" ");
oldStoreDownvotes = GM_getValue("recentDownvotes", "").split(" ");
oldStoreComments = GM_getValue("recentComments", "").split(" ");
// prepare url with user ID, ready for review page number
urlStart = "https://www.amazon.com/gp/cdp/member-reviews/" + userID + "?ie=UTF8&display=public&page=";
urlEnd = "&sort_by=MostRecentReview";
// lots of page loading and data retrieval
var perPageResponseDiv = [];
var pageResponseCount = 0;
var reviewsProcessed = 0;
var pageCount = Math.floor(reviewCount / 10) + ((reviewCount % 10 > 0) ? 1 : 0);
//var pageCount = 2; // for testing
var x = 1;
while (x <= pageCount) {
(function(x){
var urlComplete = urlStart + x + urlEnd;
perPageResponseDiv[x] = document.createElement('div');
GM_xmlhttpRequest({
method: "GET",
url: urlComplete,
onload: function(response) {
perPageResponseDiv[x].innerHTML = response.responseText;
pageResponseCount++;
// see if all data from multiple page loads has arrived
if(pageResponseCount==pageCount) {
for(y = 1; y <= pageCount; y++) {
// get parent of any reviewText DIV
var findReviews = document.evaluate("//div[@class='reviewText']/..", perPageResponseDiv[y], null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); // evaluating the doc DIV made above
for (var j = 0; j < findReviews.snapshotLength; j++) {
var oneReview = findReviews.snapshotItem(j);
var reviewChildren = oneReview.children;
var childCount = reviewChildren.length;
var commentCount = 0;
var itemTitle = "";
var itemLink = "";
var permaLink = "";
var starRating = 0;
var reviewDate = "";
var upVotes = 0;
var downVotes = 0;
var itemID = "";
// get number of comments, and permalink
var tempText = reviewChildren[childCount-2].textContent;
if(tempText.indexOf('Comment (') > -1 || tempText.indexOf('Comments (') > -1) {
var paren1 = tempText.indexOf('(');
var paren2 = tempText.indexOf(')');
commentCount = tempText.substring(paren1+2,paren2-1);
}
var lst = reviewChildren[childCount-2].getElementsByTagName('a');
permaLink = lst[2].getAttribute("href");
// get item title and item link
var lst = reviewChildren[childCount-4].getElementsByTagName('a');
itemLink = lst[0].getAttribute("href");
itemTitle = lst[0].textContent;
// the top few items to retrieve are done in a loop with IF checks,
// because the number of entries above the review varies, depending on
// whether there have been votes, if it's a verified purchase, etc.
for (var i = childCount - 5; i > -1; i--) {
// get star rating AND review date
var childHTML = reviewChildren[i].innerHTML;
var ratingClue = childHTML.indexOf('out of 5 stars');
if(ratingClue > -1) {
starRating = childHTML.substring(ratingClue-4,ratingClue-1);
reviewDate = reviewChildren[i].lastElementChild.textContent;
}
// get vote counts
var childText = reviewChildren[i].textContent;
var voteClue = childText.indexOf('people found the following review helpful');
if(voteClue > -1) {
var list = childText.trim().split(" "); // there were extra, invisible spaces!
upVotes = list[0];
var totalVotes = list[2];
downVotes = totalVotes - upVotes;
}
}
// get item ID
var lst = oneReview.parentNode.getElementsByTagName('a');
itemID = lst[0].getAttribute("name");
// get HTML formatted table row
displayBuffer += prepOneTableRow((j+1+(y-1)*10),itemID,itemTitle,permaLink,reviewDate,starRating,upVotes,downVotes,commentCount);
reviewsProcessed++;
}
}
// add footer and complete results page
displayBuffer += "<tr><td class='header-left'></td><td class='header-left'></td><td class='header-left'></td><td class='header-right'>" + (tallyStars/reviewsProcessed).toFixed(1) + "</td><td class='header-right'>" + tallyUpvotes + "</td><td class='header-right'>" + tallyDownvotes + "</td><td class='header-right'>" + helpfulPercent(tallyUpvotes,tallyDownvotes) + "</td><td class='header-right'>" + tallyComments + "</td></tr></table>";
// store info to be used in subsequent run, for change detection
GM_setValue("recentItemIDs", newStoreItemIDs.trim());
GM_setValue("recentUpvotes", newStoreUpvotes.trim());
GM_setValue("recentDownvotes", newStoreDownvotes.trim());
GM_setValue("recentComments", newStoreComments.trim());
// display results page
document.body.innerHTML = displayBuffer;
}
}
});
})(x);
x++;
}
}
function helpfulPercent(upVotes,downVotes) {
var helpfulPercent = "";
upVotes = parseInt(upVotes);
downVotes = parseInt(downVotes);
if(upVotes + downVotes > 0) helpfulPercent = (upVotes/(upVotes+downVotes)*100).toFixed(1);
return helpfulPercent;
}
function prepOneTableRow (row,itemID,itemTitle,permaLink,reviewDate,starRating,upVotes,downVotes,commentCount) {
// do these before mangling the values with <b> tags </b>
var helpfulPct = helpfulPercent(upVotes,downVotes);
itemTitle = "<a href='" + permaLink + "' target='_new'>" + itemTitle.substring(0,65) + "</a>";
// keep tallies to use in table footer
tallyUpvotes += parseInt(upVotes);
tallyDownvotes += parseInt(downVotes);
tallyStars += parseInt(starRating);
tallyComments += parseInt(commentCount);
// assemble storage info, to use in subsequent run, for change detection
newStoreItemIDs += itemID + " ";
newStoreUpvotes += upVotes + " ";
newStoreDownvotes += downVotes + " ";
newStoreComments += commentCount + " ";
// see if review for this item has previously been examined
var matchIdx = -1;
for(var i=0; i<oldStoreItemIDs.length; i++) {
if(oldStoreItemIDs[i] == itemID) {
// we have a match, an item that has previously been seen
matchIdx = i;
break;
}
}
var hiliteRow = false;
if(matchIdx > -1) {
// entry exists; see if any of the numbers have changed
if(oldStoreUpvotes[matchIdx] != upVotes) {
// for changed number, make it bold, and hilite row
upVotes = "<b>" + upVotes + "</b>";
hiliteRow = true;
}
if(oldStoreDownvotes[matchIdx] != downVotes) {
downVotes = "<b>" + downVotes + "</b>";
hiliteRow = true;
}
if(oldStoreComments[matchIdx] != commentCount) {
commentCount = "<b>" + commentCount + "</b>";
hiliteRow = true;
}
}
else {
// no match, so, it's a new review; bold the title and hilite the row
itemTitle = "<b>" + itemTitle + "</b>";
hiliteRow = true;
}
var tdLeft = ((hiliteRow===true)? "<td class='hilite-left'>" : "<td class='cell-left'>");
var tdRight = ((hiliteRow===true)? "<td class='hilite-right'>" : "<td class='cell-right'>");
var tableRow = "<tr>" + tdLeft + row + "</td>" + tdLeft + itemTitle + "</td>" + tdLeft + reviewDate + "</td>" + tdRight + starRating + "</td>" + tdRight + upVotes + "</td>" + tdRight + downVotes + "</td>" + tdRight + helpfulPct + "</td>" + tdRight + commentCount + "</td></tr>";
return tableRow;
}
//--- global event listeners allow working across scopes
document.addEventListener('click', function(event) {
var tempstr = new String(event.target);
if(tempstr.indexOf('tabulate') > -1) {
tabulate();
event.stopPropagation();
event.preventDefault();
}
}, true);
//--- main script block from this point
function Tabulate_Amazon_Reviews_Run() {
// find profile info panel
var findDiv = document.evaluate("//div[contains(.,'Helpful Votes')]", document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
var profileDiv = findDiv.snapshotItem(0);
// get reviewer ranking and user ID
var lst = profileDiv.getElementsByTagName('a');
reviewerRanking = lst[0].textContent;
var charIdx = lst[0].getAttribute("href").indexOf('#');
userID = lst[0].getAttribute("href").substring(charIdx+1);
// get helpful votes
charIdx = profileDiv.textContent.lastIndexOf(':');
helpfulVotes = profileDiv.textContent.substring(charIdx+2);
// get review count
var prevSibDiv = profileDiv.previousElementSibling;
charIdx = prevSibDiv.textContent.lastIndexOf(':');
reviewCount = prevSibDiv.textContent.substring(charIdx+2);
// add Tabulate link
profileDiv.innerHTML += "<br></br><a href='javascript:tabulate();'>Tabulate</a>";
}
Tabulate_Amazon_Reviews_Run();
})();
// End