Amazon - Show seller info

Shows country of origin and ratings for third party sellers on Amazon (and highlights Chinese sellers)

目前為 2020-04-26 提交的版本,檢視 最新版本

// ==UserScript==
// @namespace       https://openuserjs.org/users/Taddiboy
// @name            Amazon - Show seller info
// @name:de         Amazon - Verkäuferinformationen anzeigen
// @name:fr         Amazon - Afficher les informations sur le vendeur
// @name:es         Amazon - Mostrar información del vendedor
// @name:it         Amazon - Mostra info venditore
// @description     Shows country of origin and ratings for third party sellers on Amazon (and highlights Chinese sellers)
// @description:de  Zeigt Herkunftsland und Bewertungen von Drittanbietern auf Amazon (und hebt chinesische Anbieter hervor)
// @description:fr  Montre le pays d'origine et les notations des vendeurs tiers sur Amazon (et met en évidence les vendeurs chinois)
// @description:es  Muestra el país de origen y las calificaciones de los vendedores terceros en el Amazon (y destaca los vendedores chinos)
// @description:it  Mostra il paese di origine e le valutazioni per i venditori di terze parti su Amazon (e mette in evidenza i venditori cinesi)
// @copyright       2020, Taddiboy (https://openuserjs.org/users/Taddiboy)
// @license         MIT
// @author          Taddiboy
// @version         1.0.0
// @icon            https://i.imgur.com/u4v223v.png
// @include         /^https:\/\/(www|smile)\.amazon\.(co\.uk|de|es|fr|it)\/.*$/
// @grant           GM_addStyle
// @grant           GM_xmlhttpRequest
// ==/UserScript==
(function() {
  'use strict';

  // CONFIG BEGIN

  const highlightedCountries = ['CN', 'HK'];
  // Country codes as per ISO 3166 ALPHA-2
  // Set to [] to highlight no sellers at all
  // Set to ['FR'] to highlight sellers from France
  // Supported country codes: https://www.countryflags.io/#countries
  // Default: ['CN', 'HK']

  const redirectToSmile = false;
  // Available for amazon.de and amazon.co.uk
  // Set to true to redirect to Amazon Smile
  // Set to false for no redirect
  // Default: false

  const showConsoleLog = false;
  // Show errors in console log
  // Default: false

  // CONFIG END

  if (redirectToSmile && top.location.href.match(/^https:\/\/smile\.amazon\.(de|co\.uk)\//i) === null) {
    window.location.replace(window.location.href.replace(/^https:\/\/www\./i, "https://smile."));
  }

  // Check URLs for page type (search result page and best sellers page)
  const isSearchResultPage = window.location.href.match(/.*\.amazon\..*\/s\?.*/);
  const isBestsellersPage = window.location.href.match(/.*\.amazon\..*\/gp\/bestsellers\/.*/) || window.location.href.match(/.*\.amazon\..*\/Best\-Sellers\-.*/);

  if (isSearchResultPage || isBestsellersPage) {
    function showSellerCountry() {
      'use strict';

      const products = isSearchResultPage ?
        document.querySelectorAll('h2.a-size-mini.a-spacing-none.a-color-base a.a-link-normal.a-text-normal:not([data-seller])') :
        document.querySelectorAll('span.aok-inline-block.zg-item>a.a-link-normal:not([data-seller])');

      for (let i = 0; i < products.length; i++) {
        const product = products[i];
        product.setAttribute("data-seller", "set");

        if (product.href && product.href.match(/.*\.amazon\..*\/(.*\/dp|gp\/slredirect)\/.*/)) {
          GM_xmlhttpRequest({
            method: "GET",
            url: product.href,
            responseType: "document",
            onload: function(responseProduct) {
              const productPage = parse(responseProduct);
              const thirdPartySeller = productPage.querySelector('#qualifiedBuybox #sellerProfileTriggerId, #newAccordionRow #sellerProfileTriggerId, #shipsFromSoldBy_feature_div #sellerProfileTriggerId');
              const isThirdPartySeller = thirdPartySeller !== null;

              if (isThirdPartySeller) {
                thirdPartySeller.textContent = thirdPartySeller.textContent.trim();
                thirdPartySeller.href = thirdPartySeller.href.replace(/gp\/help\/seller.*/g, 'sp?' + thirdPartySeller.href.match(/(seller=.*?)(&|$)/)[1]);
                const sellerInfoLink = document.createElement("a");
                sellerInfoLink.href = thirdPartySeller.href;
                const sellerInfoContent = document.createTextNode(thirdPartySeller.textContent);
                sellerInfoLink.appendChild(sellerInfoContent);
                sellerInfoLink.classList.add("seller-info");

                isSearchResultPage ?
                  product.parentNode.parentNode.appendChild(sellerInfoLink) :
                  product.parentNode.insertBefore(sellerInfoLink, product.parentNode.querySelector('.a-icon-row.a-spacing-none'));

                GM_xmlhttpRequest({
                  method: "GET",
                  url: thirdPartySeller.href,
                  responseType: "document",
                  onload: function(responseSeller) {
                    const sellerPage = parse(responseSeller);

                    // Get seller rating
                    let rating = sellerPage.getElementById('seller-feedback-summary');
                    if (rating !== null) {
                      let ratingPercentage = '';
                      let ratingCount = '';
                      if (sellerPage.getElementById('feedback-no-rating')) {
                        ratingPercentage = sellerPage.getElementById("feedback-no-rating").textContent;
                      } else {
                        ratingPercentage = sellerPage.getElementsByClassName('feedback-detail-description')[0].textContent.match(/\d+%/);
                      }
                      if (rating.contains(sellerPage.getElementById('feedback-no-review'))) {
                        ratingCount = sellerPage.getElementById("feedback-no-review").textContent;
                      } else {
                        ratingCount = sellerPage.getElementsByClassName('feedback-detail-description')[0].textContent.match(/\(([^)]+)\)/)[1];
                      }
                      const sellerInfoRatingText = ' (' + ratingPercentage + ' | ' + ratingCount + ')';
                      const sellerInfoRating = document.createTextNode(sellerInfoRatingText);
                      sellerInfoLink.appendChild(sellerInfoRating);
                    } else {
                      if (showConsoleLog) {
                        console.log("Too many requests. Cannot  open seller pages  anymore :-\(")
                      }
                    }

                    // Get seller country & flag
                    const sellerUl = sellerPage.querySelectorAll("ul.a-unordered-list.a-nostyle.a-vertical"); //get all ul
                    if (sellerUl !== 'undefined' || 0 !== sellerUl.length) {
                      const sellerUlLast = sellerUl[sellerUl.length - 1]; //get last list
                      const sellerLi = sellerUlLast.querySelectorAll("li"); //get all li
                      const sellerLiLast = sellerLi[sellerLi.length - 1]; //get last li
                      const sellerCountry = sellerLiLast.textContent;

                      if (sellerCountry.length == 2) {
                        var flag = document.createElement("img");
                        flag.setAttribute("src", "https://www.countryflags.io/" + sellerCountry.toLowerCase() + "/flat/32.png");
                        flag.setAttribute("width", "16");
                        flag.setAttribute("height", "16");
                        flag.setAttribute("style", "margin-right: 5px;");
                        flag.title = sellerCountry;
                        sellerInfoLink.prepend(flag);

                        // Highlight sellers from countries defined in 'highlightedCountries'
                        if (highlightedCountries.includes(sellerCountry)) {
                          const outercontainer = isSearchResultPage ?
                            product.closest('.a-carousel-card, .s-result-item') :
                            product.closest('.zg-item-immersion');

                          const productImage = isSearchResultPage ?
                            outercontainer.querySelector('.s-image') :
                            outercontainer.querySelector('.zg-text-center-align img');

                          outercontainer.style.background = "linear-gradient(180deg, rgba(222,41,14,0.33) 0%, rgba(222,41,14,0) 100%)";
                          productImage.style.opacity = "0.66";
                        }
                      } else {
                        if (showConsoleLog) {
                          console.log("Seller \"" + thirdPartySeller.textContent + "\" has no valid imprint.")
                        }
                      }
                    } else {
                      if (showConsoleLog) {
                        console.log("Something went wrong while getting seller info of \"" + thirdPartySeller.textContent + "\".")
                      }
                    }
                  }
                });
              } else {
                const sellerInfoDiv = document.createElement("div");
                let soldbyAmazon = productPage.querySelector('#merchant-info').textContent.trim();
                if (!soldbyAmazon.replace(/\s/g, '').length) {
                  soldbyAmazon = '? ? ?';
                } else {
                  const svg = document.createElement("span");
                  svg.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128" width="12" height="12" style="vertical-align: bottom;margin-right: 4px;"><path fill="#f90" d="M110.2 103.3c-51.8 24.7-84 4-104.6-8.5-1.2-.8-3.4.2-1.5 2.4 6.8 8.3 29.3 28.3 58.6 28.3 29.4 0 46.9-16 49-18.8 2.2-2.7.7-4.3-1.5-3.4zm14.5-8c-1.3-1.8-8.4-2.1-12.9-1.6-4.4.5-11.1 3.3-10.5 4.9.3.6.9.3 4 0 3-.3 11.6-1.3 13.4 1 1.8 2.4-2.7 13.6-3.6 15.4-.8 1.8.3 2.3 1.8 1 1.5-1.1 4.2-4.3 6-8.7 1.8-4.4 2.9-10.6 1.8-12zm0 0"></path><path fill-rule="evenodd" d="M75.4 53c0 6.5.1 11.9-3.2 17.6-2.6 4.7-6.8 7.6-11.4 7.6-6.4 0-10.1-4.9-10.1-12 0-14.2 12.6-16.8 24.7-16.8zM92 93.5c-1.1 1-2.7 1-4 .4-5.4-4.6-6.4-6.7-9.5-11-9 9.2-15.5 12-27.3 12-14 0-24.9-8.6-24.9-25.9 0-13.5 7.3-22.6 17.7-27.1 9-4 21.6-4.7 31.3-5.8V34c0-4 .3-8.7-2-12-2-3.1-6-4.4-9.4-4.4-6.3 0-12 3.3-13.3 10-.3 1.5-1.4 3-3 3l-16-1.7c-1.4-.3-2.9-1.4-2.5-3.5C32.9 6 50.5 0 66.3 0c8.1 0 18.7 2.1 25 8.3 8.1 7.5 7.4 17.6 7.4 28.5v26c0 7.7 3.2 11.1 6.2 15.3 1.1 1.5 1.3 3.3 0 4.4L92 93.5"></path></svg>';
                  sellerInfoDiv.appendChild(svg);
                }
                const sellerInfoContent = document.createTextNode(soldbyAmazon);
                sellerInfoDiv.appendChild(sellerInfoContent);
                sellerInfoDiv.classList.add("seller-info");

                isSearchResultPage ?
                  product.parentNode.parentNode.appendChild(sellerInfoDiv) :
                  product.parentNode.insertBefore(sellerInfoDiv, product.parentNode.querySelector('.a-icon-row.a-spacing-none'));
              }
            }
          });
        }
      }
    }

    // Run script once on document ready
    showSellerCountry();

    // Initialize new MutationObserver
    const mutationObserver = new MutationObserver(showSellerCountry);

    // Let MutationObserver target the grid containing all thumbnails
    const targetNode = document.body;

    const mutationObserverOptions = {
      childList: true,
      subtree: true
    }

    // Run MutationObserver
    mutationObserver.observe(targetNode, mutationObserverOptions);

    function parse(targetPage) {
      const parser = new DOMParser();
      return parser.parseFromString(targetPage.responseText, "text/html");
    }

    // Add Google's own CSS used for image dimensions
    GM_addStyle(`
      .seller-info {
        display: inline-block;
        background: #fff;
        color: #1d1d1d !important;
        font-size: 11px;
        line-height: 15px;
        padding: 2px 5px;
        font-weight: 400;
        border: 1px solid #E0E0E0;
        margin-top: 4px;
        height: 22px;
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
        max-width: 100%;
      }
      a.seller-info:hover {
        border-color: #D0D0D0;
        text-decoration: none;
        background-color: #F3F3F3;
      }
      #zg-center-div .zg-item-immersion {
        height: 390px;
      }
    `);
  }
})();