eBay Shipping Cost Calculator

Adds shipping cost to item price in eBay search results

// ==UserScript==
// @name         eBay Shipping Cost Calculator
// @namespace    http://tampermonkey.net/
// @version      1.7
// @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
// ==/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 = `
            <strong>Shipping Calculator Settings</strong><br><br>
            <label>Tax Rate (%): <input type="number" id="ebay-tax-rate" min="0" max="100" step="0.01" value="${settings.taxRate}" style="width:60px"></label><br><br>
            <label>Text Color: <input type="color" id="ebay-total-color" value="${settings.color}"></label><br><br>
            <label>Text Size:
                <input type="range" id="ebay-total-fontsize" min="12" max="36" value="${settings.fontSize}" style="vertical-align:middle;">
                <span id="ebay-total-fontsize-value">${settings.fontSize}px</span>
            </label>
        `;

        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-item__wrapper.processed').forEach(item => {
            item.classList.remove('processed');
        });

        const items = document.querySelectorAll('.s-item__wrapper:not(.processed)');

        items.forEach(item => {
            item.classList.add('processed');

            const priceEl = item.querySelector('.s-item__price');
            if (!priceEl) return;

            const shippingEl = item.querySelector(".s-item__shipping, .s-item__paidDeliveryInfo, .s-item__freeXDays");
            if (!shippingEl) return;

            const priceText = priceEl.textContent.trim();
            const shippingText = shippingEl.textContent.trim();

            const price = parsePrice(priceText);
            const shipping = parseShipping(shippingText);

            if (price !== null && shipping !== null) {
                let total = price + shipping;
                if (settings.taxRate > 0) {
                    total += total * (settings.taxRate / 100);
                }
                let totalEl = item.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';
            }
        });

        // Update already processed items in case settings changed
        document.querySelectorAll('.s-item__wrapper.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 (text.includes('Free')) return 0;
        const match = text.match(/\+\$([\d,.]+)/);
        if (!match) return null;
        return parseFloat(match[1].replace(/,/g, ''));
    }

    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
        });
    }
})();