// ==UserScript== // @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 name, country of origin and ratings for third party sellers on Amazon (and highlights Chinese sellers) // @description:de Zeigt Name, Herkunftsland und Bewertungen von Drittanbietern auf Amazon an (und hebt chinesische Anbieter hervor) // @description:fr Montre le nom, le pays d'origine et les évaluations des vendeurs tiers sur Amazon (et met en évidence les vendeurs chinois) // @description:es Muestra el nombre, el país de origen y las valoraciones de los vendedores de terceros en el Amazon (y destaca los vendedores chinos) // @description:it Mostra il nome, il paese di origine e le valutazioni per i venditori di terze parti su Amazon (e mette in evidenza i venditori cinesi) // @namespace https://github.com/tadwohlrapp // @author Tad Wohlrapp // @version 1.1.5 // @license MIT // @homepageURL https://github.com/tadwohlrapp/amazon-show-seller-info-userscript // @supportURL https://github.com/tadwohlrapp/amazon-show-seller-info-userscript/issues // @icon https://github.com/tadwohlrapp/amazon-show-seller-info-userscript/raw/main/icon.png // @icon64 https://github.com/tadwohlrapp/amazon-show-seller-info-userscript/raw/main/icon64.png // @match https://smile.amazon.co.uk/* // @match https://www.amazon.co.uk/* // @match https://smile.amazon.de/* // @match https://www.amazon.de/* // @match https://www.amazon.es/* // @match https://www.amazon.fr/* // @match https://www.amazon.it/* // @compatible firefox Tested on Firefox v90 with Violentmonkey v2.13.0, Tampermonkey v4.13 and Greasemonkey v4.11 // @compatible chrome Tested on Chrome v94 with Violentmonkey v2.13.0 and Tampermonkey v4.13 // @downloadURL none // ==/UserScript== (function () { 'use strict'; const highlightedCountries = ['CN', 'HK']; // Country codes as per ISO 3166-1 alpha-2 // Set to [] to highlight no sellers at all // Set to ['FR'] to highlight sellers from France // Default: ['CN', 'HK'] // 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() { 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)\/.*/)) { fetch(product.href).then(function (response) { if (response.ok) { return response.text(); } }).then(function (html) { const productPage = parse(html); const thirdPartySellerSelectors = [ '#desktop_qualifiedBuyBox :not(#usedAccordionRow) #sellerProfileTriggerId', '#desktop_qualifiedBuyBox :not(#usedAccordionRow) #merchant-info a:first-of-type', '#newAccordionRow #sellerProfileTriggerId', '#newAccordionRow #merchant-info a:first-of-type' ] const thirdPartySeller = productPage.querySelector(String(thirdPartySellerSelectors)); const isThirdPartySeller = thirdPartySeller !== null; if (isThirdPartySeller) { thirdPartySeller.textContent = thirdPartySeller.textContent.trim(); thirdPartySeller.href = thirdPartySeller.href.replace(/sp\?.*/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')); fetch(thirdPartySeller.href).then(function (response) { if (response.ok) { return response.text(); } else if (response.status === 503) { throw new Error('Too many requests 🙄 Amazon blocked seller page'); } else { throw new Error(response.status); } }).then(function (html) { const sellerPage = parse(html); // Detect Amazon's 2022-04-20 redesign const sellerProfileContainer = sellerPage.getElementById('seller-profile-container'); const isRedesign = sellerProfileContainer.classList.contains('spp-redesigned'); // Get seller rating let rating = sellerPage.getElementById(isRedesign ? 'seller-feedback-summary-rd' : 'seller-feedback-summary'); 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); let sellerCountry = ''; if (isRedesign) { sellerCountry = sellerPage.querySelector('#page-section-detail-seller-info .a-box-inner .a-row:last-of-type span').textContent.toUpperCase(); } else { // Get seller country & flag const sellerUl = sellerPage.querySelectorAll('ul.a-unordered-list.a-nostyle.a-vertical'); //get all ul 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 sellerCountry = sellerLiLast.textContent.toUpperCase(); } if (sellerCountry.length == 2) { const flag = document.createElement('span'); flag.textContent = getFlagEmoji(sellerCountry); flag.title = sellerCountry; flag.classList.add('seller-flag'); 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 { console.info('Wait, that\'s illegal! 🚨 Seller "' + thirdPartySeller.textContent + '" (' + thirdPartySeller.href + ') has no valid imprint!'); } }).catch(function (err) { console.warn('Could not fetch seller data for "' + thirdPartySeller.textContent + '" (' + thirdPartySeller.href + '): ', err); }); } else { const sellerInfoDiv = document.createElement('div'); let soldbyAmazon = ''; if (productPage.querySelector('#tabular-buybox .tabular-buybox-text')) { soldbyAmazon = productPage.querySelector('#tabular-buybox .tabular-buybox-container > .tabular-buybox-text:nth-of-type(4)').textContent.trim(); } else if (!productPage.querySelector('#merchant-info')) { soldbyAmazon = '?'; } else { soldbyAmazon = productPage.querySelector('#merchant-info').textContent.trim(); } if (!soldbyAmazon.replace(/\s/g, '').length) { soldbyAmazon = '? ? ?'; } else { const svg = document.createElement('span'); svg.innerHTML = ''; 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')); } }).catch(function (err) { // There was an error console.warn('Something went wrong fetching ' + product.href, err); }); } } } // 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(html) { const parser = new DOMParser(); return parser.parseFromString(html, 'text/html'); } // Country Code to Flag Emoji (Source: https://dev.to/jorik/country-code-to-flag-emoji-a21) function getFlagEmoji(countryCode) { const codePoints = countryCode .split('') .map(char => 127397 + char.charCodeAt()); return String.fromCodePoint(...codePoints); } function addGlobalStyle(css) { const head = document.getElementsByTagName('head')[0]; if (!head) { return; } const style = document.createElement('style'); style.innerHTML = css; head.appendChild(style); } // Add Google's own CSS used for image dimensions addGlobalStyle(` .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; } span.seller-flag { font-size: 23px; line-height: 15px; vertical-align: text-top; margin-right: 5px; } #zg-center-div .zg-item-immersion { height: 390px; } `); } })();