The Amazon Review Tabulator - TART

Lists all of your reviews with vote and comment tallies, with updates highlighted

目前为 2016-10-31 提交的版本。查看 最新版本

/*=====================================================================================*\
|  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.     |
|  Click the "Tabulate" link in the "Your Profile" panel.                               |
\*=====================================================================================*/

// ==UserScript==
// @name           The Amazon Review Tabulator - TART
// @namespace      floyd.scripts
// @version        1.0.7
// @author         Another Floyd at Amazon.com
// @description    Lists all of your reviews with vote and comment tallies, with updates highlighted
// @include        https://www.amazon.com/gp/cdp/member-reviews/*
// @grant          GM_getValue
// @grant          GM_setValue
// @grant          GM_xmlhttpRequest
// @grant          GM_log
// @grant          GM_openInTab
// ==/UserScript==

// Start

(function() {

var userId = "";
var reviewCount = 0;
var reviewerRanking = "";
var helpfulVotes = 0;
var urlStart = "";
var urlEnd = "";

var oldStoreItemIDs = [];
var oldStoreUpvotes = [];
var oldStoreDownvotes = [];
var oldStoreComments = [];

var newStoreItemIDs = "";
var newStoreUpvotes = "";
var newStoreDownvotes = "";
var newStoreComments = "";

var tallyUpvotes = 0;
var tallyDownvotes = 0;
var tallyStars = 0;
var tallyComments = 0;

// use this reference for progress indicator
var profileDiv = ""; 
var profileDivOriginalHTML = "";
var profileDivTabulateHTML = "<br></br><a href='javascript:tabulate();'>Tabulate</a>";

function tabulate() {

    // reset global accumulators to ensure that repeated script runs remain clean
    newStoreItemIDs = "";
    newStoreUpvotes = "";
    newStoreDownvotes = "";
    newStoreComments = "";

    tallyUpvotes = 0;
    tallyDownvotes = 0;
    tallyStars = 0;
    tallyComments = 0;

	// set up top of display page
    var displayBuffer = "<!DOCTYPE html><html lang='en'>" +
    "<head><meta charset='utf-8'/><title>TART Amazon Review Details</title><style type='text/css'>" +
    ".tg  {border-collapse:collapse;border-spacing:0;width:100%}" +
    ".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}" +
    ".t-large {font-size:18px;font-weight:bold;font-family:Arial,sans-serif}" +
    ".t-small {font-size:8px;font-family:Arial,sans-serif}" +
    "</style></head><body>" +
    "<span class='t-large'>Amazon Review Details</span><br>" +
    "<span class='t-small'>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) + "</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 pageSetOfTableRows = [];
    var pageResponseCount = 0;
	var reviewsProcessed = 0;
    var pageCount = Math.floor(reviewCount / 10) + ((reviewCount % 10 > 0) ? 1 : 0);
    //var pageCount = 1; // for testing

    // initialize the progress indicator
    // sort of pre-redundant to do this here AND in the loop, but,
    // looks better, if there is a lag before the first response
    var progressHTML = "<br></br><b>" + pageCount + "</b>";
    profileDiv.innerHTML = profileDivOriginalHTML + progressHTML;

    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) {
				   
                    // save the incoming data
                    perPageResponseDiv[x].innerHTML = response.responseText;					
					pageResponseCount++;

                    // update the progress indicator
                    var progressHTML = "<br></br><b>" + (pageCount - pageResponseCount) + "</b>";
                    profileDiv.innerHTML = profileDivOriginalHTML + progressHTML;
                    
                    // get parent of any reviewText DIV
                    var findReviews = document.evaluate("//div[@class='reviewText']/..", perPageResponseDiv[x], null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); // evaluating the doc DIV made above

                    pageSetOfTableRows[x] = ""; // initialize each member prior to concatenating
                    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 = "No Title Available";
                        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");

                        // the data items below do not have reliable positions, due to presence
                        // or not, of vine voice tags, verified purchase, votes, etc.
                        // so, are done in a loop with IF checks. Must start loop just above review
                        // text, in case the reviewer has used any of the phrases I am searching for
                        for (var i = childCount - 4; i > -1; i--) {
                                               
                            var childHTML = reviewChildren[i].innerHTML; // used 2x, below
 
                            // get item title and item link
                            var titleClue = childHTML.indexOf('This review is from');
                            if(titleClue > -1) {
                                var lst = reviewChildren[i].getElementsByTagName('a');
                                itemLink = lst[0].getAttribute("href");
                                itemTitle = lst[0].textContent;
                            }               

                            // get star rating AND review date
                            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
                        pageSetOfTableRows[x] += prepOneTableRow((j+1+(x-1)*10),itemID,itemTitle,permaLink,reviewDate,starRating,upVotes,downVotes,commentCount);
                        
                        // clear the response, to save memory -- 
                        // could be critical when there are many review pages
                        perPageResponseDiv[x].innerHTML = "";
                        
                        reviewsProcessed++; // more reliable than reviewCount, for calculating avg. rating
                    }

					// see if all data from multiple page loads has arrived
					if(pageResponseCount==pageCount) {
 							  
                        // assemble the sets of table rows
                        for(var y=1; y <= pageCount; y++) {                      
                            displayBuffer += pageSetOfTableRows[y];
						}
                        
						// add footer and complete the 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></body></html>";
						
						// 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());
						
                        // replace progress indicator with Tabulate link
                        profileDiv.innerHTML = profileDivOriginalHTML + profileDivTabulateHTML;
                        
                        // to open new window, user must allow popups for https://www.amazon.com
                        //var resultsWindow = window.open("data:text/html," + encodeURIComponent(displayBuffer), "_blank", "scrollbars=yes");
                        //resultsWindow.focus();
                        // opening in tab does not require exception to be set by user
                        GM_openInTab("data:text/html," + encodeURIComponent(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 = "<td class='cell-left'>";
    var tdRight = "<td class='cell-right'>";
    if(hiliteRow===true && oldStoreItemIDs[0].length > 0) {
        tdLeft = "<td class='hilite-left'>";
        tdRight = "<td class='hilite-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;
}

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);
    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; also, save content for use with progress indicator
    profileDivOriginalHTML = profileDiv.innerHTML;
    profileDiv.innerHTML += profileDivTabulateHTML;
	
}
   
Tabulate_Amazon_Reviews_Run();

})();
// End

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址