- // ==UserScript==
- // @name SoldBy - Reveal Sellers on Amazon
- // @name:de SoldBy - Verkäufer auf Amazon anzeigen
- // @name:fr SoldBy - Révéler les vendeurs sur Amazon
- // @name:es SoldBy - Revelar vendedores en Amazon
- // @name:it SoldBy - Rivela i venditori su Amazon
- // @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.7.2
- // @license MIT
- // @homepageURL https://github.com/tadwohlrapp/soldby
- // @supportURL https://github.com/tadwohlrapp/soldby/issues
- // @icon https://github.com/tadwohlrapp/soldby/raw/main/userscript/img/icon.png
- // @match https://www.amazon.co.jp/*
- // @match https://www.amazon.co.uk/*
- // @match https://www.amazon.com/*
- // @match https://www.amazon.com.be/*
- // @match https://www.amazon.com.mx/*
- // @match https://www.amazon.com.tr/*
- // @match https://www.amazon.de/*
- // @match https://www.amazon.es/*
- // @match https://www.amazon.fr/*
- // @match https://www.amazon.it/*
- // @match https://www.amazon.nl/*
- // @match https://www.amazon.se/*
- // @require https://openuserjs.org/src/libs/sizzle/GM_config.min.js
- // @grant GM.getValue
- // @grant GM.setValue
- // @compatible firefox Tested on Firefox v119 with Violentmonkey v2.16.0, Tampermonkey v4.19.0 and Greasemonkey v4.11
- // @compatible chrome Tested on Chrome v119 with Violentmonkey v2.16.0 and Tampermonkey v4.19.0
- // ==/UserScript==
-
- (function () {
- 'use strict';
-
- function accessLocalStorageItems(type, deleteItems = false) {
- const entries = Object.keys(localStorage).filter(storageItem => storageItem.startsWith(type.toLowerCase() + '-'))
- if (!deleteItems) return entries.length
- const approval = confirm(`Do you really want to delete all ${entries.length} ${type}s from your browser\'s local storage? This action cannot be undone.`)
- if (!approval) return null
- entries.forEach(storageItem => {
- localStorage.removeItem(storageItem)
- })
- updateLocalStorageItemCount(type)
- alert(`Done! ${entries.length} ${type} entries were deleted`)
- }
-
- function updateLocalStorageItemCount(type) {
- GM_config.fields[`local-storage-clear-${type.toLowerCase()}`].node.value = `Delete ${accessLocalStorageItems(type)} ${type}s from local storage`
- }
-
- const frame = document.createElement('div');
- frame.classList.add('sb-options');
-
- const backdrop = document.createElement('div');
- backdrop.classList.add('sb-options--backdrop');
-
- document.body.appendChild(frame);
- document.body.appendChild(backdrop);
-
- GM_config.init({
- 'id': 'sb-settings',
- 'title': 'SoldBy Settings',
- 'fields': {
- 'countries': {
- 'section': ['Countries to highlight',
- 'Country codes as per ISO 3166-1 alpha-2, separated by a comma or space.'],
- 'label': 'List of country codes',
- 'type': 'text',
- 'default': 'CN, HK'
- },
- 'unknown': {
- 'section': ['Highlight undetectable countries',
- 'Some sellers have incomplete/invalid profiles with their country of origin missing.'],
- 'label': 'Highlight products sold from unknown countries',
- 'type': 'checkbox',
- 'default': true
- },
- 'hide': {
- 'section': ['Hide products instead of highlighting them',
- 'If you get overwhelmed by the sheer mass of highlighted product listings, maybe you want to hide them completely.'],
- 'label': 'Yes, hide all highlighted products',
- 'type': 'checkbox',
- 'default': false
- },
- 'max-asin-age': {
- 'section': ['Check every x days if the seller for a product has changed',
- 'For better performance, SoldBy caches product-seller relationships in your browser\'s local storage for one day (24 hours). You can change that value here.'],
- 'label': 'Number of days after which a product ASIN should be re-fetched',
- 'type': 'int',
- 'default': 1
- },
- 'max-seller-age': {
- 'section': ['Check every x days if a seller has new ratings (or has moved countries)',
- 'For better performance, SoldBy caches a seller\'s ratings and country in your browser\'s local storage for one week (7 days). You can change that value here.'],
- 'label': 'Number of days after which a seller\'s info should be re-fetched',
- 'type': 'int',
- 'default': 7
- },
- 'local-storage-clear-asin': {
- 'section': ['Clear SoldBy data from my browser\'s local storage',
- 'If you feel your browser\'s local storage might be stuffed with too much of SoldBy\'s data, you can delete the items here.'],
- 'label': ' ',
- 'type': 'button',
- 'size': 25,
- 'click': function () { accessLocalStorageItems('ASIN', true) }
- },
- 'local-storage-clear-seller': {
- 'label': ' ',
- 'type': 'button',
- 'value': 'test',
- 'size': 25,
- 'click': function () { accessLocalStorageItems('Seller', true) }
- },
- },
- 'events': {
- 'init': onInit,
- 'open': () => {
- GM_config.frame.removeAttribute('style');
- backdrop.style.display = 'block';
-
- const buttons = frame.querySelectorAll('button');
- wrapBtn(buttons[0], true);
- wrapBtn(buttons[1]);
-
- backdrop.addEventListener('click', () => {
- GM_config.close();
- });
- document.onkeydown = function (evt) {
- evt = evt || window.event;
- var isEscape = false;
- if ("key" in evt) isEscape = (evt.key === "Escape" || evt.key === "Esc");
- if (isEscape) GM_config.close();
- };
- updateLocalStorageItemCount('ASIN')
- updateLocalStorageItemCount('Seller')
- },
- 'save': () => {
- GM_config.close();
- },
- 'close': () => {
- backdrop.removeAttribute('style');
- },
- },
- 'frame': frame
- });
-
- function onInit() {
- GM_config.css.basic = '';
-
- // Link to Settings in Footer:
- try {
- const settingsLink = document.createElement('button');
- const navFooterCopyright = document.querySelector('.navFooterCopyright');
- navFooterCopyright.appendChild(settingsLink);
- settingsLink.addEventListener('click', () => { GM_config.open(); });
- settingsLink.textContent = '⚙️ SoldBy';
- wrapBtn(settingsLink, false, true);
- } catch {
- console.error('Could not add settings link');
- }
-
- const countriesArr = GM_config.get('countries').split(/(?:,| )+/);
- if (GM_config.get('unknown')) countriesArr.push('?');
-
- const options = {
- maxAgeAsinFetch: GM_config.get('max-asin-age'),
- maxAgeSellerFetch: GM_config.get('max-seller-age'),
- highlightedCountries: countriesArr,
- hideHighlightedProducts: GM_config.get('hide')
- };
-
- function showSellerCountry() {
-
- // Gets the ASIN for every visible product and sets it as "data-asin" attribute
- getAsin();
-
- // Identify products by looking for "data-asin" attribute
- const productsWithAsinSelectors = [
- 'div[data-asin]',
- 'not([data-asin=""])',
- 'not([data-seller-name])',
- 'not([data-uuid*=s-searchgrid-carousel])',
- 'not([role="img"])',
- 'not(#averageCustomerReviews)',
- 'not(#detailBullets_averageCustomerReviews)',
- 'not(.inline-twister-swatch)',
- 'not(.contributorNameID)',
- 'not(.a-hidden)',
- 'not(.rpi-learn-more-card-content)',
- 'not(#reviews-image-gallery-container)',
- 'not([class*=_cross-border-widget_style_preload-widget])',
- 'not([data-video-url])'
- ];
- const products = document.querySelectorAll(productsWithAsinSelectors.join(':'));
-
- // If no new products are found, return.
- if (products.length == 0) return;
-
- products.forEach((product) => {
-
- // Give each product the data-seller-name attribute to prevent re-capturing.
- product.dataset.sellerName = 'loading...';
-
- createInfoBox(product);
-
- if (localStorage.getItem(asinKey(product))) {
- getSellerIdAndNameFromLocalStorage(product);
- } else {
- getSellerIdAndNameFromProductPage(product);
- }
- });
- }
-
- // 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');
- }
-
- function getAsin() {
-
- // Check current page for products (without "data-asin" attribute)
- const productSelectors = [
- '.a-carousel-card > div:not([data-asin])',
- '.octopus-pc-item:not([data-asin])',
- 'li[class*=ProductGridItem__]:not([data-asin])',
- 'div[class*=_octopus-search-result-card_style_apbSearchResultItem]:not([data-asin])',
- '.sbv-product:not([data-asin])',
- '.a-cardui #gridItemRoot:not([data-asin])'
- ];
- const products = document.querySelectorAll(String(productSelectors));
-
- // If no new products are found, return.
- if (products.length == 0) return;
-
- products.forEach((product) => {
-
- // Take the first link but not if it's inside the "Bestseller" container (links to bestsellers page instead of product page) and not if it has the popover-trigger class, as its href is just "javascript:void(0)" (hidden feedback form on sponsored products)
- const link = product.querySelector('a:not(.s-grid-status-badge-container > a):not(.a-popover-trigger)');
-
- // If link cannot be found, return
- if (!link) return;
-
- link.href = decodeURIComponent(link.href);
- let asin = '';
- const searchParams = new URLSearchParams(link.href);
- if (searchParams.get('pd_rd_i')) {
- asin = searchParams.get('pd_rd_i')
- } else if (/\/dp\/(.*?)($|\?|\/)/.test(link.href)) {
- asin = link.href.match(/\/dp\/(.*?)($|\?|\/)/)[1]
- }
- product.dataset.asin = asin;
- });
- }
-
- function getSellerIdAndNameFromLocalStorage(product) {
- const { sid: sellerId, sn: sellerName, ts: timeStamp } = JSON.parse(localStorage.getItem(asinKey(product)));
-
- validateItemAge(product, timeStamp, 'asin');
-
- if (sellerId) product.dataset.sellerId = sellerId;
- product.dataset.sellerName = sellerName;
- setSellerDetails(product);
- }
-
- function getSellerIdAndNameFromProductPage(product, refetch = false) {
- // fetch seller, get data, save in local storage, set attributes
-
- if (!product.dataset.asin) return;
-
- if (refetch) console.log('Re-fetching ' + asinKey(product) + ' from product page');
-
- const link = window.location.origin + '/dp/' + product.dataset.asin + '?psc=1';
-
- fetch(link).then(function (response) {
- if (response.ok) {
- return response.text();
- }
- }).then(function (html) {
- const productPage = parse(html);
-
- let sellerId, sellerName;
-
- // weed out various special product pages:
- const specialPageSelectors = [
- '#gc-detail-page', /* gift card sold by amazon */
- '.reload_gc_balance', /* reload amazon balance */
- '#dp.digitaltextfeeds, #dp.magazine, #dp.ebooks, #dp.audible', /* magazines, subscriptions, audible, etc */
- '.av-page-desktop, .avu-retail-page' /* prime video */
- ];
-
- if (productPage.querySelector(String(specialPageSelectors))) {
- sellerName = 'Amazon';
- } else {
- // find third party seller mention on product page
- const thirdPartySellerSelectors = [
- '#desktop_qualifiedBuyBox :not(#usedAccordionRow) #sellerProfileTriggerId',
- '#desktop_qualifiedBuyBox :not(#usedAccordionRow) #merchant-info a:first-of-type',
- '#exports_desktop_qualifiedBuybox :not(#usedAccordionRow) #sellerProfileTriggerId',
- '#exports_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));
-
- if (thirdPartySeller) {
-
- // Get seller ID
- const searchParams = new URLSearchParams(thirdPartySeller.href);
- sellerId = searchParams.get('seller');
- const sellerUrl = window.location.origin + '/sp?seller=' + sellerId;
-
- // Get seller Name
- sellerName = thirdPartySeller.textContent.trim().replaceAll('"', '“');
- } else {
-
- let queryMerchantName = ' ';
- if (productPage.querySelector('#tabular-buybox .tabular-buybox-text')) {
- queryMerchantName = productPage.querySelector('#tabular-buybox .tabular-buybox-container > .tabular-buybox-text:last-of-type').textContent.trim();
- } else if (productPage.querySelector('#merchant-info')) {
- queryMerchantName = productPage.querySelector('#merchant-info').textContent.trim();
- } else if (productPage.querySelector('[offer-display-feature-name="desktop-merchant-info"]')) {
- queryMerchantName = productPage.querySelector('[offer-display-feature-name="desktop-merchant-info"]').textContent.trim();
- }
-
- if (queryMerchantName.replace(/\s/g, '').length) {
- sellerName = 'Amazon';
- } else {
- sellerName = '? ? ?';
- }
- }
- }
-
- // Set data-seller-name attribute
- product.dataset.sellerName = sellerName;
-
- if (sellerId) {
- // If seller is known: set ASIN with corresponding seller in local storage
- localStorage.setItem(asinKey(product), `{"sid":"${sellerId}","sn":"${sellerName}","ts":"${Date.now()}"}`);
- // Set data-seller-id attribute
- product.dataset.sellerId = sellerId;
- }
-
- if (sellerName == 'Amazon') {
- localStorage.setItem(asinKey(product), `{"sn":"${sellerName}","ts":"${Date.now()}"}`);
- }
-
- setSellerDetails(product);
-
- }).catch(function (err) {
- console.warn('Something went wrong fetching ' + link, err);
- });
- }
-
- function setSellerDetails(product) {
- if (product.dataset.sellerName.includes('Amazon') || product.dataset.sellerName == '? ? ?') {
- populateInfoBox(product);
- return; // if seller is Amazon or unknown, no further steps are needed
- }
-
- if (localStorage.getItem(sellerKey(product))) {
- getSellerCountryAndRatingfromLocalStorage(product);
- } else {
- getSellerCountryAndRatingfromSellerPage(product);
- }
- }
-
- function getSellerCountryAndRatingfromLocalStorage(product) {
-
- // seller key found in local storage
- const { c: country, rs: ratingScore, rc: ratingCount, ts: timeStamp } = JSON.parse(localStorage.getItem(sellerKey(product)));
-
- validateItemAge(product, timeStamp, 'seller');
-
- product.dataset.sellerCountry = country;
- product.dataset.sellerRatingScore = ratingScore;
- product.dataset.sellerRatingCount = ratingCount;
-
- highlightProduct(product);
- populateInfoBox(product);
- }
-
- function getSellerCountryAndRatingfromSellerPage(product, refetch = false) {
- // seller key not found in local storage. fetch seller details from seller-page
-
- if (refetch) console.log('Re-fetching ' + sellerKey(product) + ' from product page');
-
- // build seller link
- const link = window.location.origin + '/sp?seller=' + product.dataset.sellerId;
-
- fetch(link).then(function (response) {
- if (response.ok) {
- return response.text();
- } else if (response.status === 503) {
- product.dataset.blocked = true;
- populateInfoBox(product);
- throw new Error('🙄 Too many requests. Amazon blocked seller page. Please try again in a few minutes.');
- } else {
- throw new Error(response.status);
- }
- }).then(function (html) {
-
- let seller = getSellerDetailsFromSellerPage(parse(html));
- // --> seller.country (e.g. 'US')
- // --> seller.rating.score (e.g. '69%')
- // --> seller.rating.count (e.g. '420')
-
- // Set attributes: data-seller-country, data-seller-rating-score and data-seller-rating-count
- product.dataset.sellerCountry = seller.country;
- product.dataset.sellerRatingScore = seller.rating.score;
- product.dataset.sellerRatingCount = seller.rating.count;
-
- // Write to local storage
- localStorage.setItem(sellerKey(product), `{"c":"${seller.country}","rs":"${seller.rating.score}","rc":"${seller.rating.count}","ts":"${Date.now()}"}`);
-
- highlightProduct(product);
- populateInfoBox(product);
-
- }).catch(function (err) {
- console.warn('Could not fetch seller data for "' + product.dataset.sellerName + '" (' + link + '):', err);
- });
- }
-
- function getSellerDetailsFromSellerPage(sellerPage) {
- // Detect Amazon's 2022-04-20 redesign
- const sellerProfileContainer = sellerPage.getElementById('seller-profile-container');
- const isRedesign = sellerProfileContainer.classList.contains('spp-redesigned');
-
- const country = getSellerCountryFromSellerPage(sellerPage, isRedesign); // returns DE
- const rating = getSellerRatingFromSellerPage(sellerPage); // returns 91%
-
- return { country, rating };
- }
-
- function getSellerCountryFromSellerPage(sellerPage, isRedesign) {
- let country;
- if (isRedesign) {
- let addressArr = sellerPage.querySelectorAll('#page-section-detail-seller-info .a-box-inner .a-row.a-spacing-none.indent-left');
- country = addressArr[addressArr.length - 1]?.textContent.toUpperCase();
- } else {
- try {
- 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
- country = sellerLiLast.textContent.toUpperCase();
- } catch {
- return '?';
- }
- }
- return (/^[A-Z]{2}$/.test(country)) ? country : '?';
- }
-
- function getSellerRatingFromSellerPage(sellerPage) {
- if (sellerPage.getElementById('seller-name').textContent.includes('Amazon')) {
- return false; // seller is Amazon subsidiary and doesn't display ratings
- }
-
- let feedbackEl = sellerPage.getElementById('seller-info-feedback-summary')
- let text = feedbackEl.querySelector('.feedback-detail-description').textContent
- let starText = feedbackEl.querySelector('.a-icon-alt').textContent
- text = text.replace(starText, '')
- let regex = /(\d+%).*?\((\d+)/;
- let zeroPercent = '0%';
-
- const lang = document.documentElement.lang
- // Turkish places the percentage sign in front (e.g. %89)
- if (lang === 'tr-tr') {
- regex = /(%\d+).*?\((\d+)/;
- zeroPercent = '%0';
- }
-
- // Special treatment for amazon.de in German and amazon.com.be in French
- if (lang === 'de-de' || lang === 'fr-be') {
- regex = /(\d+ %).*?\((\d+)/;
- zeroPercent = '0 %';
- }
-
- let rating = text.match(regex);
- let score = rating ? rating[1] : zeroPercent;
- let count = rating ? rating[2] : '0';
-
- return { score, count };
- }
-
- function highlightProduct(product) {
- if (!options.highlightedCountries.includes(product.dataset.sellerCountry)) return;
-
- // Highlight sellers from countries defined in 'options.highlightedCountries'
- product.classList.add('product--highlight');
-
- if (!options.hideHighlightedProducts) return;
-
- // When hideHighlightedProducts is true: Find correct element to hide
- let hiddenElement = product;
- if (product.parentElement.classList.contains('a-carousel-card')) {
- hiddenElement = product.parentElement;
- }
- if (product.closest('.sbx-desktop') !== null) {
- hiddenElement = product.closest('.sbx-desktop');
- }
- if (product.classList.contains('sbv-product')) {
- hiddenElement = product.closest('.s-result-item');
- }
- hiddenElement.classList.add('sb--hide');
- }
-
- function createInfoBox(product) {
- const infoBoxCt = document.createElement('div');
- infoBoxCt.classList.add('seller-info-ct', 'a-size-small');
-
- const infoBox = document.createElement('div');
- infoBox.classList.add('seller-info');
-
- const icon = document.createElement('div');
- icon.classList.add('seller-icon', 'seller-loading');
- infoBox.appendChild(icon);
-
- const text = document.createElement('div');
- text.classList.add('seller-text');
- text.textContent = product.dataset.sellerName;
- infoBox.appendChild(text);
-
- infoBoxCt.appendChild(infoBox);
-
- let productTitle = findTitle(product);
-
- if (productTitle) {
- productTitle.parentNode.insertBefore(infoBoxCt, productTitle.nextSibling);
- } else {
- product.appendChild(infoBoxCt);
- }
-
- fixHeights(product);
- }
-
- function populateInfoBox(product) {
- const container = product.querySelector('.seller-info-ct');
- const infoBox = container.querySelector('.seller-info');
- const icon = container.querySelector('.seller-icon');
- const text = container.querySelector('.seller-text');
-
- // remove loading spinner
- icon.classList.remove('seller-loading');
-
- // replace "loading..." with real seller name
- text.textContent = product.dataset.sellerName;
-
- if (product.dataset.sellerId && product.dataset.sellerId !== 'Amazon') {
- // Create link to seller profile if sellerId is valid
- const anchor = document.createElement('a');
- anchor.classList.add('seller-link');
- anchor.appendChild(infoBox);
- container.appendChild(anchor);
- anchor.href = window.location.origin + '/sp?seller=' + product.dataset.sellerId;
- }
-
- if (product.dataset.blocked) {
- icon.textContent = '⚠️';
- icon.style.fontSize = "1.5em";
- infoBox.title = 'Error 503: Too many requests. Amazon blocked seller page. Please try again in a few minutes.';
- return;
- }
-
- if (product.dataset.sellerName.includes('Amazon')) {
- // Seller is Amazon or one of its subsidiaries (Warehouse, UK, US, etc.)
- const amazonIcon = document.createElement('img');
- amazonIcon.src = '/favicon.ico';
- icon.appendChild(amazonIcon);
- infoBox.title = product.dataset.sellerName;
- return;
- }
-
- // 1. Set icon, create infoBox title (if country known)
- if (product.dataset.sellerCountry && product.dataset.sellerCountry != '?') {
- icon.textContent = getFlagEmoji(product.dataset.sellerCountry);
- infoBox.title = (new Intl.DisplayNames([document.documentElement.lang], { type: 'region' })).of(product.dataset.sellerCountry) + ' | ';
- } else {
- icon.textContent = '❓';
- icon.style.fontSize = "1.5em";
- }
-
- if (!product.dataset.sellerId) {
- console.error('No seller found', product);
- return;
- }
-
- // 2. Append name to infoBox title
- infoBox.title += product.dataset.sellerName;
-
- // 3. Append rating to text and infoBox title
- const ratingText = `(${product.dataset.sellerRatingScore} | ${product.dataset.sellerRatingCount})`;
- text.textContent += ` ${ratingText}`;
- infoBox.title += ` ${ratingText}`;
- }
-
- function findTitle(product) {
- //TODO switch case
- try {
- let title;
- if (product.dataset.avar) {
- title = product.querySelector('.a-color-base.a-spacing-none.a-link-normal');
- } else if (product.parentElement.classList.contains('a-carousel-card')) {
- if (product.classList.contains('a-section') && product.classList.contains('a-spacing-none')) {
- title = product.querySelector('.a-link-normal');
- } else if (product.querySelector('.a-truncate:not([data-a-max-rows="1"])') !== null) {
- title = product.querySelector('.a-truncate');
- } else if (product.querySelector('h2') !== null) {
- title = product.getElementsByTagName("h2")[0];
- } else {
- title = product.querySelectorAll('.a-link-normal')[1];
- }
- } else if (product.id == 'gridItemRoot' || product.closest('#zg') !== null) {
- title = product.querySelectorAll('.a-link-normal')[1];
- } else if (product.classList.contains('octopus-pc-item-v3')) {
- title = product.querySelectorAll('.octopus-pc-asin-title, .octopus-pc-dotd-title')[0];
- } else if (product.classList.contains('octopus-pc-lightning-deal-item-v3')) {
- title = product.querySelector('.octopus-pc-deal-title');
- } else if (product.querySelector('.sponsored-products-truncator-truncated') !== null) {
- title = product.querySelector('.sponsored-products-truncator-truncated');
- } else {
- title = product.getElementsByTagName("h2")[0];
- }
- return title;
- } catch (error) {
- console.error(error, product);
- }
- }
-
- function fixHeights(product) {
- // fixes for grid-item:
- if (product.id == 'gridItemRoot') {
- product.style.height = product.offsetHeight + 20 + 'px';
- }
-
- if (product.classList.contains('octopus-pc-item')) {
-
- const els = document.querySelectorAll('.octopus-pc-card-height-v3, .octopus-dotd-height, .octopus-lightning-deal-height');
- for (const el of els) {
- if (!el.getAttribute('style')) el.style.height = el.offsetHeight + 30 + 'px';
- }
-
- const text = product.querySelectorAll('.octopus-pc-deal-block-section, .octopus-pc-dotd-info-section')[0];
- if (text) text.style.height = text.offsetHeight + 30 + 'px';
-
- if (product.classList.contains('octopus-pc-lightning-deal-item-v3') && !product.dataset.height) {
- product.style.setProperty('height', product.offsetHeight + 30 + 'px', 'important');
- product.dataset.height = 'set';
- }
- }
-
- if (product.closest('#rhf') !== null && product.closest('.a-carousel-viewport') !== null) {
- const els = document.querySelectorAll('.a-carousel-viewport, .a-carousel-left, .a-carousel-right');
- for (const el of els) {
- if (el.getAttribute('style') && !el.dataset.height) {
- el.style.height = el.offsetHeight + 30 + 'px';
- el.dataset.height = 'set';
- }
- }
- }
-
- // hide stupid blocking links on sponsored products
- if (product.closest('.sbx-desktop') !== null) {
- const links = product.querySelectorAll('a:empty');
- links.forEach((link) => {
- link.style.height = 0;
- });
- }
- }
-
- function asinKey(product) {
- return 'asin-' + product.dataset.asin;
- }
-
- function sellerKey(product) {
- return 'seller-' + product.dataset.sellerId
- }
-
- // validate storage item age and trigger re-fetch if needed
- function validateItemAge(product, itemTimeStamp, itemType) {
- const currentItemAge = Date.now() - parseInt(itemTimeStamp);
- let allowedItemAge, key, refetchFunction;
-
- switch (itemType) {
- case 'asin':
- allowedItemAge = options.maxAgeAsinFetch * 1000 * 3600 * 24;
- key = asinKey;
- refetchFunction = getSellerIdAndNameFromProductPage;
- break;
- case 'seller':
- allowedItemAge = options.maxAgeSellerFetch * 1000 * 3600 * 24;
- key = sellerKey;
- refetchFunction = getSellerCountryAndRatingfromSellerPage;
- break;
- }
-
- if (currentItemAge > allowedItemAge) {
- console.warn('Storage item ' + key(product) + ' is ' + readableItemAge(currentItemAge) + ' old. We must re-fetch it');
- return refetchFunction(product, true);
- }
- }
- }
-
- // 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);
- }
-
- // wrap function to create buttons with amazon's ui
- function wrapBtn(el, primary = false, small = false) {
- const wrapper = document.createElement('span');
- el.classList.add('a-button-inner', 'a-button-text');
- wrapper.classList.add('a-button');
- if (primary) wrapper.classList.add('a-button-primary');
- if (small) wrapper.classList.add('a-button-small');
- el.parentNode.insertBefore(wrapper, el);
- wrapper.appendChild(el);
- }
-
- // convert storage item age from millisecs to days and hours
- function readableItemAge(ms) {
- const days = Math.floor(ms / (24 * 60 * 60 * 1000));
- const daysms = ms % (24 * 60 * 60 * 1000);
- const hours = Math.floor(daysms / (60 * 60 * 1000));
- return days + ' days and ' + hours + ' hours';
- }
-
- function addGlobalStyle(css) {
- const head = document.getElementsByTagName('head')[0];
- if (!head) return;
- const style = document.createElement('style');
- style.innerHTML = css;
- head.appendChild(style);
- }
-
- addGlobalStyle(`
- .sb--hide {
- display: none !important;
- }
-
- .seller-info-ct {
- cursor: default;
- margin-top: 4px;
- }
-
- .seller-info {
- display: inline-flex;
- gap: 4px;
- background: #fff;
- font-size: 0.9em;
- padding: 2px 4px;
- border: 1px solid #d5d9d9;
- border-radius: 4px;
- max-width: 100%;
- }
-
- .seller-loading {
- display: inline-block;
- width: 0.8em;
- height: 0.8em;
- border: 3px solid rgb(255 153 0 / 30%);
- border-radius: 50%;
- border-top-color: #ff9900;
- animation: spin 1s ease-in-out infinite;
- margin: 1px 3px 0;
- }
-
- @keyframes spin {
- to {
- transform: rotate(360deg);
- }
- }
-
- .seller-icon {
- vertical-align: text-top;
- text-align: center;
- font-size: 1.8em;
- }
-
- .seller-icon svg {
- width: 0.8em;
- height: 0.7em;
- }
-
- .seller-icon img {
- width: 0.82em;
- height: 0.82em;
- }
-
- .seller-text {
- color: #1d1d1d !important;
- white-space: nowrap;
- text-overflow: ellipsis;
- overflow: hidden;
- }
-
- a.seller-link:hover .seller-info {
- box-shadow: 0 2px 5px 0 rgb(213 217 217 / 50%);
- background-color: #f7fafa;
- border-color: #d5d9d9;
- }
-
- a.seller-link:hover .seller-name {
- text-decoration: underline;
- }
-
- .product--highlight .s-card-container,
- .product--highlight[data-avar],
- .product--highlight.sbv-product,
- .a-carousel-has-buttons .product--highlight,
- #gridItemRoot.product--highlight,
- #gridItemRoot.product--highlight .a-cardui,
- .product--highlight .octopus-pc-item-image-section,
- .product--highlight .octopus-pc-asin-info-section,
- .product--highlight .octopus-pc-deal-block-section,
- .product--highlight .octopus-pc-dotd-info-section,
- .acswidget-carousel .product--highlight .acs-product-block {
- background-color: #f9e3e4;
- border-color: #f9e3e4;
- }
-
- #gridItemRoot.product--highlight,
- .product--highlight .s-card-border {
- border-color: #e3abae;
- }
-
- .product--highlight .s-card-drop-shadow {
- box-shadow: none;
- border: 1px solid #e3abae;
- }
-
- .product--highlight .s-card-drop-shadow .s-card-border {
- border-color: #f9e3e4;
- }
-
- .product--highlight[data-avar],
- .a-carousel-has-buttons .product--highlight {
- padding: 0 2px;
- box-sizing: content-box;
- }
-
- .product--highlight.zg-carousel-general-faceout,
- #rhf .product--highlight {
- box-shadow: inset 0 0 0 1px #e3abae;
- padding: 0 6px;
- word-break: break-all;
- }
-
- .product--highlight.zg-carousel-general-faceout img,
- #rhf .product--highlight img {
- max-width: 100% !important;
- }
-
- #rhf .product--highlight img {
- margin: 1px auto -1px;
- }
-
- .product--highlight a,
- .product--highlight .a-color-base,
- .product--highlight .a-price[data-a-color='base'] {
- color: #842029 !important;
- }
-
- #gridItemRoot .seller-info {
- margin-bottom: 6px;
- }
-
- .octopus-pc-item-v3 .seller-info-ct,
- .octopus-pc-lightning-deal-item-v3 .seller-info-ct {
- padding: 4px 20px 0;
- }
-
- .sbx-desktop .seller-info-ct {
- margin: 0;
- }
-
- .sp-shoveler .seller-info-ct {
- margin: -2px 0 3px;
- }
-
- .p13n-sc-shoveler .seller-info-ct {
- margin: 0;
- }
-
- .octopus-pc-item-image-section-v3 {
- text-align: center;
- }
-
- #rhf .a-section.a-spacing-mini {
- text-align: center;
- }
-
- a:hover.a-color-base,
- a:hover.seller-link {
- text-decoration: none;
- }
-
- .sb-options {
- display: none;
- left: 50%;
- margin: 5vh auto;
- max-height: 90vh;
- max-width: 80vw;
- overflow-y: auto;
- position: fixed;
- top: 0;
- transform: translateX(-50%);
- width: 666px;
- z-index: 999;
- background-color: white;
- padding: 1rem;
- }
-
- .sb-options--backdrop {
- display: none;
- position: fixed;
- top: 0;
- left: 0;
- bottom: 0;
- right: 0;
- background-color: rgba(0, 0, 0, 0.4);
- user-select: none;
- z-index: 199;
- }
-
- #sb-settings * {
- font-family: inherit;
- }
-
- #sb-settings .config_var {
- display: flex;
- flex-direction: row;
- justify-content: flex-start;
- }
-
- #sb-settings .config_var input[type="checkbox"] {
- margin-right: 4px;
- min-width: 13px;
- }
-
- #sb-settings #sb-settings_countries_var,
- #sb-settings #sb-settings_max-asin-age_var,
- #sb-settings #sb-settings_max-seller-age_var {
- flex-direction: column-reverse;
- }
-
- #sb-settings .config_header {
- font-size: 165%;
- line-height: 32px;
- }
-
- #sb-settings_header::before {
- content: '';
- display: inline-block;
- width: 32px;
- height: 32px;
- background: url(https://github.com/tadwohlrapp/soldby/raw/main/userscript/img/icon.png);
- background-size: contain;
- margin-right: 12px;
- vertical-align: bottom;
- }
-
- #sb-settings .config_header,
- #sb-settings .config_var {
- padding-bottom: 16px;
- margin-bottom: 16px;
- border-bottom: 1px solid #ccc;
- }
-
- #sb-settings .section_header {
- font-size: 110%;
- font-weight: 500;
- }
-
- #sb-settings .section_desc {
- color: #666;
- background: #fff;
- }
-
- #sb-settings label {
- font-weight: 500;
- }
-
- #sb-settings_buttons_holder {
- display: flex;
- flex-direction: row-reverse;
- gap: 12px;
- align-items: center;
- }
-
- #sb-settings #sb-settings_local-storage-clear-asin_var,
- #sb-settings #sb-settings_local-storage-clear-seller_var{
- width: 50%;
- display: inline-flex;
- }
-
- #sb-settings #sb-settings_local-storage-clear-seller_var {
- justify-content: flex-end;
- }
- `);
- })();
-