Amazon Search++ (Full Tooltips & Logic)

Amazon filter/sort tools and clean URL copier with smart tooltips

// ==UserScript==
// @name         Amazon Search++ (Full Tooltips & Logic)
// @namespace    http://www.amazon.com
// @version      2.4
// @description  Amazon filter/sort tools and clean URL copier with smart tooltips
// @author       Mikhail Aukslang
// @match        *://www.amazon.*/s*
// @match        https://*.amazon.ca/*
// @grant        GM_setClipboard
// @license      CC BY-NC-SA 4.0
// @run-at       document-end
// ==/UserScript==

(function () {
  'use strict';

  let mustContainWords = [];
  let excludeWords = [];

  const isSearchPage = /\/s(\?|$)/i.test(location.pathname + location.search);
  const isProductPage = /\/dp\/[A-Z0-9]{10}/.test(location.pathname);

  const container = document.createElement('div');
  container.style = `
    position: fixed;
    bottom: 10px;
    right: 10px;
    background: white;
    border: 2px solid #555;
    border-radius: 8px;
    padding: 10px;
    font-size: 13px;
    font-family: sans-serif;
    z-index: 99999;
    max-width: 280px;
    box-shadow: 0 2px 10px rgba(0,0,0,0.3);
    opacity: 0.95;
  `;
  container.innerHTML = `
    <strong>🛠️ Amazon Filter</strong><br>
    <input type="text" id="mustInput" placeholder="✅ Must contain (Enter)" style="width:100%;margin-top:5px;" title="Type in a word that the results MUST contain then press [Enter]">
    <div id="mustList" style="margin-bottom:6px;"></div>
    <input type="text" id="notInput" placeholder="🚫 Must NOT contain (Enter)" style="width:100%;" title="Type in a word that the results MUST NOT contain then press [Enter]">
    <div id="notList" style="margin-bottom:6px;"></div>
    <label title="Hide listings that are out of stock.">
      <input type="checkbox" id="hideOOS"> 📦 Hide Out-of-Stock
    </label><br>
    <label title="I noticed Amazon nerfs the search results when you sort by price, this option bypasses that giving you much better results">
      <input type="checkbox" id="sortByPrice"> 💰 Sort by Price
    </label><br>
    <button id="applyBtn" style="margin-top:6px;width:100%;" title="Apply all filters now">🎯 Apply Filters</button>
    <button id="copyBtn" style="margin-top:5px;width:100%;" title="Sharing Amazon listings by default includes a bunch of tracking crap, this button copies to your clipboard just the bare minimum of the URL necessary for sharing purposes">🔗 Copy Clean URL</button>
  `;
  document.body.appendChild(container);

  const mustInput = document.getElementById('mustInput');
  const notInput = document.getElementById('notInput');
  const mustListDiv = document.getElementById('mustList');
  const notListDiv = document.getElementById('notList');
  const hideOOS = document.getElementById('hideOOS');
  const sortByPrice = document.getElementById('sortByPrice');
  const applyBtn = document.getElementById('applyBtn');
  const copyBtn = document.getElementById('copyBtn');

  function applyTooltip(el, message) {
    el.title = message;
  }

  function toggleUI(enableFilters, enableCopy) {
    const disable = (el, msg) => {
      el.disabled = true;
      el.style.opacity = '0.3';
      el.style.pointerEvents = 'none';
      if (msg) applyTooltip(el, msg);
    };
    const enable = el => {
      el.disabled = false;
      el.style.opacity = '1.0';
      el.style.pointerEvents = 'auto';
      el.removeAttribute('title');
    };

    const filterControls = [
      { el: mustInput, msg: 'Only available on search result pages.' },
      { el: notInput, msg: 'Only available on search result pages.' },
      { el: hideOOS, msg: 'Only available on search result pages.' },
      { el: sortByPrice, msg: 'Only available on search result pages.' },
      { el: applyBtn, msg: 'Only available on search result pages.' },
      { el: mustListDiv, msg: '' },
      { el: notListDiv, msg: '' }
    ];

    const copyControl = { el: copyBtn, msg: 'Only available on product pages (e.g., /dp/ASIN)' };

    filterControls.forEach(({ el, msg }) => enableFilters ? enable(el) : disable(el, msg));
    enableCopy ? enable(copyControl.el) : disable(copyControl.el, copyControl.msg);
  }

  if (isProductPage) {
    toggleUI(false, true);
  } else if (isSearchPage) {
    toggleUI(true, false);
  }

  function updateWordListDisplay() {
    mustListDiv.innerHTML = '';
    notListDiv.innerHTML = '';

    mustContainWords.forEach(word => {
      const div = document.createElement('div');
      div.innerHTML = `✅ ${word} <span style="color:red;cursor:pointer;">❌</span>`;
      div.querySelector('span').addEventListener('click', () => {
        mustContainWords = mustContainWords.filter(w => w !== word);
        updateWordListDisplay();
        filterProducts();
      });
      mustListDiv.appendChild(div);
    });

    excludeWords.forEach(word => {
      const div = document.createElement('div');
      div.innerHTML = `🚫 ${word} <span style="color:red;cursor:pointer;">❌</span>`;
      div.querySelector('span').addEventListener('click', () => {
        excludeWords = excludeWords.filter(w => w !== word);
        updateWordListDisplay();
        filterProducts();
      });
      notListDiv.appendChild(div);
    });
  }

  function extractPrice(product) {
    const priceEl = product.querySelector('.a-price .a-offscreen');
    return priceEl ? parseFloat(priceEl.textContent.replace(/[^0-9.]/g, '')) || Infinity : Infinity;
  }

  function filterProducts() {
    const items = document.querySelectorAll('[data-component-type="s-search-result"]');

    items.forEach(item => {
      const text = item.innerText.toLowerCase();
      const title = item.querySelector('h2')?.innerText.toLowerCase() || '';
      const outOfStock = /currently unavailable|out of stock|no featured offers/i.test(text);

      const matchMust = mustContainWords.length === 0 || mustContainWords.some(w => title.includes(w));
      const matchNot = excludeWords.length === 0 || excludeWords.every(w => !title.includes(w));

      if (matchMust && matchNot && (!hideOOS.checked || !outOfStock)) {
        item.style.display = '';
      } else {
        item.style.display = 'none';
      }
    });

    if (sortByPrice.checked) {
      const slot = document.querySelector('.s-main-slot');
      if (!slot) return;
      const visible = Array.from(slot.children).filter(x => x.style.display !== 'none');
      visible.sort((a, b) => extractPrice(a) - extractPrice(b));
      visible.forEach(el => slot.appendChild(el));
    }
  }

  mustInput.addEventListener('keydown', (e) => {
    if (e.key === 'Enter' && mustInput.value.trim()) {
      mustContainWords.push(mustInput.value.trim().toLowerCase());
      mustInput.value = '';
      updateWordListDisplay();
      filterProducts();
    }
  });

  notInput.addEventListener('keydown', (e) => {
    if (e.key === 'Enter' && notInput.value.trim()) {
      excludeWords.push(notInput.value.trim().toLowerCase());
      notInput.value = '';
      updateWordListDisplay();
      filterProducts();
    }
  });

  applyBtn.addEventListener('click', filterProducts);

  copyBtn.addEventListener('click', () => {
    const match = location.href.match(/https:\/\/www\.amazon\.[^\/]+\/[^\/]+\/dp\/[A-Z0-9]{10}/);
    if (match) {
      GM_setClipboard(match[0]);
      alert('🔗 Clean URL copied:\n' + match[0]);
    } else {
      alert('❌ Not a valid product URL.');
    }
  });

})();