Amazon to eBay Sidebar Enhancer Ultimate

Show eBay matches on Amazon product pages with inline or floating UI, sorting, persistent manual search, drag+snap gear, theme toggle, and optional copy button and styling cleanup for maximum polish and tweakability.

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Amazon to eBay Sidebar Enhancer Ultimate
// @namespace    Eliminater74
// @version      1.4.0
// @description  Show eBay matches on Amazon product pages with inline or floating UI, sorting, persistent manual search, drag+snap gear, theme toggle, and optional copy button and styling cleanup for maximum polish and tweakability.
// @author       Eliminater74
// @license      MIT
// @match        https://www.amazon.com/*
// @grant        GM_xmlhttpRequest
// @connect      www.ebay.com
// @run-at       document-end
// ==/UserScript==

(function () {
    'use strict';

    const SETTINGS_KEY = 'amazonEbayEnhancerSettings';
    const POSITION_KEY = 'amazonEbayEnhancerPositions';
    const SEARCH_KEY = 'amazonEbayManualSearch';
    const defaultSettings = {
        showPanel: true,
        showImages: true,
        theme: 'auto',
        maxResults: 5,
        sortMode: 'relevance',
        injectInline: false,
        manualSearch: false,
        showCopyButton: true
    };

    let settings = JSON.parse(localStorage.getItem(SETTINGS_KEY)) || defaultSettings;
    let positions = JSON.parse(localStorage.getItem(POSITION_KEY)) || {};
    let manualSearchText = localStorage.getItem(SEARCH_KEY) || '';

    function saveSettings() {
        localStorage.setItem(SETTINGS_KEY, JSON.stringify(settings));
    }

    function savePositions() {
        localStorage.setItem(POSITION_KEY, JSON.stringify(positions));
    }

    function saveSearchOverride(text) {
        localStorage.setItem(SEARCH_KEY, text);
    }

    function getTheme() {
        return settings.theme === 'auto'
            ? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light')
            : settings.theme;
    }

    function applyTheme(el) {
        const theme = getTheme();
        el.classList.add(`ebay-enhancer-${theme}`);
    }

    function waitForTitle(cb) {
        const el = document.querySelector('#productTitle');
        if (el) cb(el.textContent.trim());
        else setTimeout(() => waitForTitle(cb), 400);
    }

    function fetchEbayResults(query, callback) {
        showLoadingBox();
        const sort = settings.sortMode === 'price' ? '&_sop=15' : '&_sop=12';
        const url = `https://www.ebay.com/sch/i.html?_nkw=${encodeURIComponent(query)}${sort}`;
        GM_xmlhttpRequest({
            method: 'GET',
            url,
            onload: res => {
                const doc = new DOMParser().parseFromString(res.responseText, 'text/html');
                const items = [...doc.querySelectorAll('.s-item')].slice(0, settings.maxResults).map(el => ({
                    title: el.querySelector('.s-item__title')?.textContent || 'No Title',
                    price: el.querySelector('.s-item__price')?.textContent || 'N/A',
                    link: el.querySelector('a.s-item__link')?.href || '#',
                    image: el.querySelector('.s-item__image-img')?.src || null
                }));
                hideLoadingBox();
                callback(items);
            }
        });
    }

    function showLoadingBox() {
        const box = document.createElement('div');
        box.id = 'ebay-loading-box';
        box.className = 'ebay-enhancer-box';
        box.textContent = '🔍 Searching eBay for matches...';
        document.body.appendChild(box);
    }

    function hideLoadingBox() {
        const box = document.getElementById('ebay-loading-box');
        if (box) box.remove();
    }

    function createStyle() {
        const style = document.createElement('style');
        style.textContent = `
            .ebay-enhancer-light { background: #f9f9f9; color: #000; }
            .ebay-enhancer-dark  { background: #1e1e1e; color: #f0f0f0; }
            .ebay-enhancer-panel {
                position: fixed;
                width: 330px;
                max-height: 70vh;
                overflow-y: auto;
                border: 2px solid #999;
                padding: 10px;
                border-radius: 6px;
                box-shadow: 0 0 10px rgba(0,0,0,0.3);
                z-index: 99999;
                cursor: move;
            }
            .ebay-enhancer-gear {
                position: fixed;
                width: 40px;
                height: 40px;
                font-size: 22px;
                background: #222;
                color: #fff;
                border: 2px solid #888;
                border-radius: 50%;
                display: flex;
                justify-content: center;
                align-items: center;
                cursor: move;
                z-index: 2147483647;
            }
            .ebay-enhancer-config {
                position: fixed;
                background: #fff;
                border: 1px solid #ccc;
                padding: 10px;
                border-radius: 8px;
                display: none;
                z-index: 999999;
            }
            .ebay-enhancer-box {
                position: fixed;
                top: 100px;
                right: 20px;
                padding: 12px 16px;
                font-size: 14px;
                font-weight: bold;
                border-radius: 6px;
                border: 2px solid #ccc;
                z-index: 999999;
            }
        `;
        document.head.appendChild(style);
    }

    function makeDraggable(el, id) {
        let isDragging = false, offsetX = 0, offsetY = 0;
        el.addEventListener('mousedown', e => {
            isDragging = true;
            el.dataset.dragging = true;
            offsetX = e.clientX - el.getBoundingClientRect().left;
            offsetY = e.clientY - el.getBoundingClientRect().top;
            e.preventDefault();
        });
        document.addEventListener('mousemove', e => {
            if (!isDragging) return;
            const left = e.clientX - offsetX;
            const top = e.clientY - offsetY;
            const width = window.innerWidth, height = window.innerHeight;

            if (left < width / 2) {
                el.style.left = '10px'; el.style.right = 'unset';
                positions[`${id}Left`] = '10px'; delete positions[`${id}Right`];
            } else {
                el.style.right = '10px'; el.style.left = 'unset';
                positions[`${id}Right`] = '10px'; delete positions[`${id}Left`];
            }

            if (top < height / 2) {
                el.style.top = '10px'; el.style.bottom = 'unset';
                positions[`${id}Top`] = '10px'; delete positions[`${id}Bottom`];
            } else {
                el.style.bottom = '10px'; el.style.top = 'unset';
                positions[`${id}Bottom`] = '10px'; delete positions[`${id}Top`];
            }

            savePositions();
        });
        document.addEventListener('mouseup', () => {
            isDragging = false;
            delete el.dataset.dragging;
        });
    }

    function buildResultHTML(results) {
        return results.map(r => `
            <div style="margin-bottom:12px;">
                <a href="${r.link}" target="_blank" style="font-weight:bold; color:#0073e6; text-decoration:none;">${r.title}</a><br/>
                <span>${r.price}</span><br/>
                ${settings.showImages && r.image ? `<img src="${r.image}" style="width:100px; margin-top:4px;" />` : ''}
            </div>`).join('');
    }

    function createResultsPanel(results) {
        const wrapper = document.createElement('div');
        wrapper.id = 'ebay-enhancer-panel';
        wrapper.innerHTML = `<h3 style="margin-top:0;">🛒 eBay Matches</h3>` + buildResultHTML(results);

        if (settings.showCopyButton) {
            const btn = document.createElement('button');
            btn.textContent = '📋 Copy All to Clipboard';
            btn.style.cssText = 'margin-top:10px;padding:5px;font-size:13px;';
            btn.onclick = () => {
                const text = results.map(r => `${r.title}\n${r.price}\n${r.link}\n`).join('\n');
                navigator.clipboard.writeText(text).then(() => alert("Copied!"));
            };
            wrapper.appendChild(btn);
        }

        if (settings.injectInline) {
            const target = document.getElementById('productTitle');
            if (target) {
                const box = document.createElement('div');
                box.style.marginTop = '20px';
                box.style.border = '1px solid #ccc';
                box.style.padding = '10px';
                applyTheme(box);
                box.appendChild(wrapper);
                target.parentNode.appendChild(box);
            }
        } else {
            applyTheme(wrapper);
            wrapper.classList.add('ebay-enhancer-panel');
            wrapper.style.top = positions.panelTop || '80px';
            wrapper.style.left = positions.panelLeft || 'unset';
            wrapper.style.right = positions.panelRight || '10px';
            makeDraggable(wrapper, 'panel');
            document.body.appendChild(wrapper);
        }
    }

    function createGearIcon() {
        const gear = document.createElement('div');
        gear.className = 'ebay-enhancer-gear';
        gear.textContent = '⚙️';
        gear.style.top = positions.gearTop || 'unset';
        gear.style.left = positions.gearLeft || 'unset';
        gear.style.bottom = positions.gearBottom || '20px';
        gear.style.right = positions.gearRight || '20px';

        const panel = document.createElement('div');
        panel.className = 'ebay-enhancer-config';
        panel.style.bottom = '70px';
        panel.style.right = '20px';

        panel.innerHTML = `
            <label><input type="checkbox" id="ebayToggle" ${settings.showPanel ? 'checked' : ''}/> Show Panel</label><br/>
            <label><input type="checkbox" id="imageToggle" ${settings.showImages ? 'checked' : ''}/> Show Images</label><br/>
            <label><input type="checkbox" id="inlineToggle" ${settings.injectInline ? 'checked' : ''}/> Inject Inline</label><br/>
            <label><input type="checkbox" id="manualToggle" ${settings.manualSearch ? 'checked' : ''}/> Manual Search</label><br/>
            <label><input type="checkbox" id="copyToggle" ${settings.showCopyButton ? 'checked' : ''}/> Show Copy Button</label><br/>
            <label>Sort:
                <select id="sortMode">
                    <option value="relevance" ${settings.sortMode === 'relevance' ? 'selected' : ''}>Relevance</option>
                    <option value="price" ${settings.sortMode === 'price' ? 'selected' : ''}>Price ↑</option>
                </select>
            </label><br/>
            <label>Theme:
                <select id="themeSelect">
                    <option value="auto" ${settings.theme === 'auto' ? 'selected' : ''}>Auto</option>
                    <option value="light" ${settings.theme === 'light' ? 'selected' : ''}>Light</option>
                    <option value="dark" ${settings.theme === 'dark' ? 'selected' : ''}>Dark</option>
                </select>
            </label><br/>
            <label>Max Results: <input type="number" id="maxResults" value="${settings.maxResults}" style="width:50px"/></label><br/>
            <input type="text" id="manualInput" placeholder="Search override..." value="${manualSearchText}" style="width:100%;margin-top:5px;"/>
        `;

        gear.addEventListener('click', () => {
            if (!gear.dataset.dragging)
                panel.style.display = panel.style.display === 'none' ? 'block' : 'none';
        });

        const idMap = {
            ebayToggle: 'showPanel',
            imageToggle: 'showImages',
            inlineToggle: 'injectInline',
            manualToggle: 'manualSearch',
            copyToggle: 'showCopyButton'
        };

        for (const id in idMap) {
            panel.querySelector(`#${id}`).addEventListener('change', e => {
                settings[idMap[id]] = e.target.checked;
                saveSettings();
                location.reload();
            });
        }

        panel.querySelector('#sortMode').addEventListener('change', e => {
            settings.sortMode = e.target.value;
            saveSettings();
            location.reload();
        });

        panel.querySelector('#themeSelect').addEventListener('change', e => {
            settings.theme = e.target.value;
            saveSettings();
            location.reload();
        });

        panel.querySelector('#maxResults').addEventListener('change', e => {
            settings.maxResults = parseInt(e.target.value) || 5;
            saveSettings();
            location.reload();
        });

        panel.querySelector('#manualInput').addEventListener('input', e => {
            saveSearchOverride(e.target.value);
        });

        makeDraggable(gear, 'gear');
        document.body.appendChild(gear);
        document.body.appendChild(panel);
    }

    createStyle();
    waitForTitle(title => {
        const query = settings.manualSearch ? manualSearchText || title : title;
        if (settings.showPanel) {
            fetchEbayResults(query, createResultsPanel);
        }
        createGearIcon();
    });
})();