// ==UserScript==
// @name eBay Shipping Cost Calculator
// @namespace http://tampermonkey.net/
// @version 1.8
// @description Adds shipping cost to item price in eBay search results
// @author none
// @match https://www.ebay.com/sch/*
// @icon https://www.ebay.com/favicon.ico
// @grant none
// @downloadURL https://update.greasyfork.icu/scripts/513999/eBay%20Shipping%20Cost%20Calculator.user.js
// @updateURL https://update.greasyfork.icu/scripts/513999/eBay%20Shipping%20Cost%20Calculator.meta.js
// ==/UserScript==
(function() {
'use strict';
let processing = false;
let debounceTimer;
// --- Settings ---
let settings = {
taxRate: parseFloat(localStorage.getItem('ebayTaxRate')) || 0,
color: localStorage.getItem('ebayTotalColor') || '#e42648',
fontSize: localStorage.getItem('ebayTotalFontSize') || '18'
};
// --- Settings UI ---
function createSettingsButton() {
const button = document.createElement('div');
button.id = 'ebay-settings-button';
button.style.position = 'fixed';
button.style.top = '10px';
button.style.right = '10px';
button.style.width = '20px';
button.style.height = '20px';
button.style.borderRadius = '50%';
button.style.backgroundColor = '#e42648';
button.style.cursor = 'pointer';
button.style.zIndex = '10000';
button.style.boxShadow = '0 2px 4px rgba(0,0,0,0.2)';
document.body.appendChild(button);
button.addEventListener('click', toggleSettingsWindow);
}
function createSettingsWindow() {
const settingsDiv = document.createElement('div');
settingsDiv.id = 'ebay-shipping-settings';
settingsDiv.style.position = 'fixed';
settingsDiv.style.top = '40px';
settingsDiv.style.right = '20px';
settingsDiv.style.zIndex = '2147483647';
settingsDiv.style.background = 'white';
settingsDiv.style.border = '1px solid #ccc';
settingsDiv.style.padding = '16px';
settingsDiv.style.borderRadius = '8px';
settingsDiv.style.boxShadow = '0 2px 8px rgba(0,0,0,0.15)';
settingsDiv.style.fontFamily = 'Arial, sans-serif';
settingsDiv.style.minWidth = '220px';
settingsDiv.style.display = 'none';
document.addEventListener('click', function(event) {
const settingsDiv = document.getElementById('ebay-shipping-settings');
const settingsButton = document.getElementById('ebay-settings-button');
if (settingsDiv && settingsDiv.style.display === 'block') {
// Check if click is outside both settings window and button
if (!settingsDiv.contains(event.target) && !settingsButton.contains(event.target)) {
settingsDiv.style.display = 'none';
}
}
});
settingsDiv.innerHTML = `
Shipping Calculator Settings
`;
document.body.appendChild(settingsDiv);
// Event listeners
document.getElementById('ebay-tax-rate').addEventListener('input', function() {
settings.taxRate = parseFloat(this.value) || 0;
localStorage.setItem('ebayTaxRate', settings.taxRate);
addShippingToPrices(); // Always update on input
});
document.getElementById('ebay-total-color').addEventListener('input', function() {
settings.color = this.value;
localStorage.setItem('ebayTotalColor', settings.color);
addShippingToPrices();
});
document.getElementById('ebay-total-fontsize').addEventListener('input', function() {
settings.fontSize = this.value;
localStorage.setItem('ebayTotalFontSize', settings.fontSize);
document.getElementById('ebay-total-fontsize-value').textContent = `${settings.fontSize}px`;
addShippingToPrices();
});
}
function toggleSettingsWindow() {
const settingsDiv = document.getElementById('ebay-shipping-settings');
if (settingsDiv) {
settingsDiv.style.display = settingsDiv.style.display === 'none' ? 'block' : 'none';
}
}
// --- Main logic ---
function addShippingToPrices() {
if (processing) return;
processing = true;
// Remove processed class to force recalculation on all items
document.querySelectorAll('.s-card__attribute-row.processed').forEach(row => {
row.classList.remove('processed');
});
// For each price row
document.querySelectorAll('.s-card__attribute-row:not(.processed)').forEach(priceRow => {
const priceEl = priceRow.querySelector('.s-card__price');
if (!priceEl) return;
// Look ahead for a shipping row with 'delivery'
let nextRow = priceRow.nextElementSibling;
let shippingEl = null;
while (nextRow) {
if (nextRow.classList.contains('s-card__attribute-row')) {
shippingEl = Array.from(nextRow.querySelectorAll('span')).find(
el => el.textContent.toLowerCase().includes('delivery')
);
if (shippingEl) break;
}
nextRow = nextRow.nextElementSibling;
}
if (!shippingEl) return;
const price = parsePrice(priceEl.textContent);
const shipping = parseShipping(shippingEl.textContent);
if (price !== null && shipping !== null) {
let total = price + shipping;
if (settings.taxRate > 0) {
total += total * (settings.taxRate / 100);
}
// Insert or update total display after priceEl
let totalEl = priceRow.querySelector('.s-item__total');
if (!totalEl) {
totalEl = document.createElement('div');
totalEl.className = 's-item__total';
priceEl.parentNode.insertBefore(totalEl, priceEl.nextSibling);
}
totalEl.textContent = `Total: $${total.toFixed(2)}`;
totalEl.style.color = settings.color;
totalEl.style.fontWeight = 'bold';
totalEl.style.fontSize = settings.fontSize + 'px';
}
priceRow.classList.add('processed');
});
// Update already processed items in case settings changed
document.querySelectorAll('.s-card__attribute-row.processed .s-item__total').forEach(totalEl => {
totalEl.style.color = settings.color;
totalEl.style.fontWeight = 'bold';
totalEl.style.fontSize = settings.fontSize + 'px';
});
processing = false;
}
function parsePrice(text) {
const match = text.match(/\$([\d,.]+)/);
if (!match) return null;
return parseFloat(match[1].replace(/,/g, ''));
}
function parseShipping(text) {
if (/free/i.test(text)) return 0;
// Match $xx.xx before 'delivery' (with or without extra text after)
const match = text.match(/\$([\d,.]+)\s+delivery/i);
if (match) return parseFloat(match[1].replace(/,/g, ''));
// Fallback: match any $xx.xx in the string
const fallback = text.match(/\$([\d,.]+)/);
if (fallback) return parseFloat(fallback[1].replace(/,/g, ''));
return null;
}
function handleMutations() {
clearTimeout(debounceTimer);
debounceTimer = setTimeout(addShippingToPrices, 300);
}
// --- Init ---
createSettingsButton();
createSettingsWindow();
addShippingToPrices();
const container = document.querySelector('.srp-river-main');
if (container) {
const observer = new MutationObserver(handleMutations);
observer.observe(container, {
childList: true,
subtree: false,
attributes: false,
characterData: false
});
}
})();