The Amazon Review Tabulator - TART

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

目前为 2016-11-03 提交的版本。查看 最新版本

/*===========================================================================*\
|  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. See item on script host menu, on browser toolbar, to activate       |
|  Alternate Display Mode, which allows toggle between seeing all reviews, or |
|  only those with recent activity.                                           |
\*===========================================================================*/

// ==UserScript==
// @name           The Amazon Review Tabulator - TART
// @namespace      floyd.scripts
// @version        1.1.2
// @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
// @grant          GM_registerMenuCommand
// ==/UserScript==

// Start

(function() {

    var alternateDisplayMode = false;
    var showUpdatesOnly = false;
    var primaryDisplayBuffer = "";
    var updateDisplayBuffer = "";

    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;

        var toggleLink = (alternateDisplayMode) ? "<br><a href='javascript:toggleView();'>Toggle View: All Reviews | Updates Only</a>" : "";

        // set up top of display page
        primaryDisplayBuffer = "<!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:9px;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) + toggleLink +
            "</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>";

        updateDisplayBuffer = primaryDisplayBuffer; // for alternate display mode

        // 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 = 3; // 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+1,paren2);
                                commentCount = parseInt(commentCount.replace(/,/g, '')); // remove commas
                            }
                            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 = parseInt(list[0].replace(/,/g, '')); // remove commas
                                    var totalVotes = parseInt(list[2].replace(/,/g, ''));
                                    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++) {
                                primaryDisplayBuffer += pageSetOfTableRows[y];
                            }

                            // add footer and complete the results page
                            primaryDisplayBuffer += "<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>";

                            // get rows containing updated reviews, only
                            var tempDiv = document.createElement('div');
                            tempDiv.innerHTML = primaryDisplayBuffer;

                            var findNonUpdateRows = document.evaluate("//td[@class='hilite-left']/..", tempDiv, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);

                            for(var d = 0; d < findNonUpdateRows.snapshotLength; d++) {
                                updateDisplayBuffer += findNonUpdateRows.snapshotItem(d).outerHTML;
                            }
                            updateDisplayBuffer += "</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;

                            // --- a display alternative - put results IN the Amazon page
                            //document.body.innerHTML = primaryDisplayBuffer;

                            // --- to open new window, user must allow popups for https://www.amazon.com
                            //var resultsWindow = window.open("data:text/html," + encodeURIComponent(primaryDisplayBuffer), "_blank", "scrollbars=yes");

                            // --- using GM_openInTab does not require exception to be set by user
                            if(!alternateDisplayMode) GM_openInTab("data:text/html," + encodeURIComponent(primaryDisplayBuffer));
                            else document.body.innerHTML = primaryDisplayBuffer;
                        }
                    }
                });
            })(x);
            x++;
        }
    }

    function alternateDisplayModeToggle() {
        alternateDisplayMode = !alternateDisplayMode;
        GM_setValue("AltDisplayMode", alternateDisplayMode);
        alert("Alternate Display Mode = " + alternateDisplayMode);
    }

    function toggleView() {
        showUpdatesOnly = !showUpdatesOnly;
        //alert("toggled!");
        if(showUpdatesOnly) document.body.innerHTML = updateDisplayBuffer;
        else document.body.innerHTML = primaryDisplayBuffer;
    }

    function helpfulPercent(upVotes,downVotes) {
        var helpfulPercent = "";
        upVotes = upVotes;
        downVotes = 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 += upVotes;
        tallyDownvotes += downVotes;
        tallyStars += parseInt(starRating);
        tallyComments += 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();
        }

        if(tempstr.indexOf('toggleView') > -1) {
            toggleView();
            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;

        // add item to the User Script Command menu, to toggle display mode
        GM_registerMenuCommand(" TART Alternate Display Mode Toggle", alternateDisplayModeToggle);
        alternateDisplayMode = GM_getValue("AltDisplayMode", false);

    }

    Tabulate_Amazon_Reviews_Run();

})();
// End

QingJ © 2025

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