Auto-sort popular by default; re-applies on every search results update (SPA/pagination).
// ==UserScript==
// @name Amazon Plus (UK)
// @namespace http://tampermonkey.net/
// @version 5.5
// @description Auto-sort popular by default; re-applies on every search results update (SPA/pagination).
// @author Gemini (and You!)
// @license MIT
// @match *://www.amazon.co.uk/*
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_setClipboard
// @run-at document-idle
// ==/UserScript==
(function() {
'use strict';
// --- UTILS ---
// Safe wrappers so the script degrades gracefully if GM_* APIs are missing
const safeGMGet = (key, def) => {
try {
if (typeof GM_getValue === 'function') {
return GM_getValue(key, def);
}
} catch (e) {
console.warn('GM_getValue failed, falling back to localStorage.', e);
}
try {
const raw = window.localStorage.getItem(`amz_plus_${key}`);
if (raw === null) return def;
return JSON.parse(raw);
} catch {
return def;
}
};
const safeGMSet = (key, value) => {
try {
if (typeof GM_setValue === 'function') {
GM_setValue(key, value);
}
} catch (e) {
console.warn('GM_setValue failed, falling back to localStorage.', e);
}
try {
window.localStorage.setItem(`amz_plus_${key}`, JSON.stringify(value));
} catch {
// ignore storage failures
}
};
const getVal = (id, def) => safeGMGet(id, def);
const setVal = (id, val) => safeGMSet(id, val);
const FEATURE_DEFAULTS = {
amz_master: true, // global ON/OFF switch (ON by default)
amz_sort: true, // Auto-Sort Popular: ON by default on every search page
amz_ads: true,
amz_purist: false
};
/** Debounced re-run so SPA / pagination / lazy results don’t miss a sort pass */
let searchApplyTimer = null;
let searchObserver = null;
const SEARCH_APPLY_DEBOUNCE_MS = 120;
function scheduleSearchApply() {
if (isProductPage()) return;
if (searchApplyTimer) clearTimeout(searchApplyTimer);
searchApplyTimer = setTimeout(() => {
searchApplyTimer = null;
try {
setupSearchResultsWatcher(); // attach once `.s-main-slot` exists (late DOM)
applySortAndFilter();
} catch (e) {
console.warn('Amazon Plus: applySortAndFilter failed', e);
}
}, SEARCH_APPLY_DEBOUNCE_MS);
}
function setupSearchResultsWatcher() {
if (isProductPage()) return;
const container = document.querySelector('.s-main-slot') || document.querySelector('.s-result-list');
if (!container || searchObserver) return;
searchObserver = new MutationObserver(() => scheduleSearchApply());
searchObserver.observe(container, { childList: true, subtree: true });
}
let historyHooksInstalled = false;
function hookHistoryForSearchPages() {
if (historyHooksInstalled) return;
historyHooksInstalled = true;
const notify = () => scheduleSearchApply();
window.addEventListener('popstate', notify);
const wrap = (fn) =>
function stateWrapped() {
const ret = fn.apply(this, arguments);
scheduleSearchApply();
return ret;
};
if (history.pushState) history.pushState = wrap(history.pushState);
if (history.replaceState) history.replaceState = wrap(history.replaceState);
}
const getFlag = (key) => {
const def = Object.prototype.hasOwnProperty.call(FEATURE_DEFAULTS, key)
? FEATURE_DEFAULTS[key]
: false;
const stored = getVal(key, null);
return stored === null ? def : stored;
};
function isProductPage() {
return window.location.pathname.includes('/dp/') || window.location.pathname.includes('/gp/product/');
}
function extractRatingCount(el) {
const node =
el.querySelector('.haul-puis-ratings-container .a-size-micro:last-child') ||
el.querySelector('span[aria-label*="ratings"]') ||
el.querySelector('.s-underline-text') ||
el.querySelector('[aria-label*="ratings"]');
if (!node) return 0;
const raw = (node.innerText || node.getAttribute('aria-label') || '0').replace(/,/g, '');
const re = /([\d.]+)\s*(K)?/gi;
let max = 0;
let m;
while ((m = re.exec(raw)) !== null) {
let val = parseFloat(m[1]);
if (!Number.isFinite(val)) continue;
if (m[2]) val *= 1000;
if (val > max) max = val;
}
return Math.round(max) || 0;
}
// --- SEARCH PAGE ENGINE ---
function applySortAndFilter() {
if (isProductPage()) return;
const container = document.querySelector('.s-main-slot') || document.querySelector('.s-result-list');
if (!container) return;
const items = container.querySelectorAll('.s-result-item[data-asin]');
if (items.length === 0) return;
// Master switch off: restore defaults and exit.
if (!getFlag('amz_master')) {
items.forEach((item) => {
if (!item.getAttribute('data-asin')) return;
item.style.display = '';
item.style.order = '';
});
return;
}
const doSort = getFlag('amz_sort');
const doAds = getFlag('amz_ads');
const doPurist = getFlag('amz_purist');
items.forEach(item => {
if (!item.getAttribute('data-asin')) return;
const text = item.innerText;
const isSponsored = item.classList.contains('AdHolder') || text.includes('Sponsored');
const isThirdParty = doPurist && !text.includes('Prime') && !text.includes('Amazon');
if ((doAds && isSponsored) || isThirdParty) {
item.style.display = 'none';
} else {
item.style.display = '';
}
item.style.order = doSort ? -extractRatingCount(item) : '';
});
}
// --- SIDEBAR UI (SEARCH PAGE) ---
function injectSidebar() {
if (isProductPage() || document.getElementById('god-mode-panel')) return;
const sidebar = document.querySelector('#s-refinements') || document.querySelector('.sf-left-nav-reflow');
if (!sidebar) return;
const panel = document.createElement('div');
panel.id = 'god-mode-panel';
panel.style.cssText = "background: #fdfdfd; padding: 12px; border: 2px solid #B12704; border-radius: 6px; margin-bottom: 15px; font-family: Arial;";
const createToggle = (id, label) => `
<div style="display:flex; justify-content:space-between; margin-bottom:8px; font-size:12px; color:#0f1111;">
<b>${label}</b>
<input type="checkbox" id="${id}" ${getFlag(id) ? 'checked' : ''} style="cursor:pointer;">
</div>`;
panel.innerHTML = `
<div style="font-weight:900; border-bottom:1px solid #ddd; margin-bottom:10px; padding-bottom:5px; color:#B12704;">⚡ GOD MODE TOOLS</div>
${createToggle('amz_master', '⏻ On/Off Switch')}
${createToggle('amz_sort', '📊 Auto-Sort Popular')}
${createToggle('amz_ads', '🚫 Block Sponsored Ads')}
${createToggle('amz_purist', '🏭 STRICT: Amazon/Prime Only')}
`;
sidebar.insertAdjacentElement('afterbegin', panel);
panel.querySelectorAll('input').forEach(input => {
input.addEventListener('change', (e) => {
const id = e.target.id;
setVal(id, e.target.checked);
if (id === 'amz_master') {
// When master toggles, rerun engines so both product/search behavior update.
runEngines();
} else {
applySortAndFilter();
}
});
});
// Ensure defaults are written once so future checks are consistent
Object.keys(FEATURE_DEFAULTS).forEach((key) => {
if (getVal(key, null) === null) {
setVal(key, FEATURE_DEFAULTS[key]);
}
});
}
// --- PRODUCT PAGE: DASHBOARD ---
function injectProductDashboard() {
if (!isProductPage() || document.getElementById('god-mode-dashboard')) return;
const targetContainer = document.querySelector('#titleSection') || document.querySelector('#centerCol');
if (!targetContainer) return;
const asinMatch = window.location.href.match(/\/(?:dp|gp\/product)\/([A-Z0-9]{10})/);
const asin = asinMatch ? asinMatch[1] : '';
if (!asin) return;
const pageText = document.documentElement.textContent;
const bsrMatch = pageText.match(/Best Sellers Rank:?\s*#?([0-9,]+)\s+in\s+([A-Za-z&\s]+)/i);
const bsrText = bsrMatch ? `🏆 <b>BSR:</b> #${bsrMatch[1]} in ${bsrMatch[2].trim().substring(0, 20)}...` : '<span style="color:#888;">No BSR found</span>';
const isNonReturnable = pageText.includes('Non-returnable') || pageText.includes('not returnable');
const returnHtml = isNonReturnable ? `<div style="width:100%; background:#ffe3e3; color:#B12704; padding:6px; border:1px solid #B12704; font-weight:bold; text-align:center; margin-bottom:8px; border-radius:4px; font-size:12px;">🚨 WARNING: This item is NON-RETURNABLE</div>` : '';
const hasUsed = pageText.includes('Used from') || document.querySelector('#olpLinkWidget_feature_div');
const warehouseHtml = hasUsed ? `<a href="https://www.amazon.co.uk/dp/${asin}#aod" style="background:#007185; color:white; padding:4px 8px; border-radius:4px; text-decoration:none; font-weight:bold;">♻️ Open Box Deals</a>` : '';
let ppuHtml = '';
const ppuNode = document.querySelector('.a-size-small.a-color-price');
if (ppuNode && ppuNode.textContent.includes('/')) {
ppuHtml = `<span style="background:#fff3d4; color:#B12704; font-weight:900; padding:2px 6px; border-radius:4px; border:1px solid #f4bf42;">⚖️ ${ppuNode.textContent.trim()}</span>`;
ppuNode.style.display = 'none';
}
const mainImg = document.querySelector('#landingImage') || document.querySelector('#imgBlkFront');
const lensLink = mainImg ? `https://lens.google.com/uploadbyurl?url=${encodeURIComponent(mainImg.src)}` : '#';
const dash = document.createElement('div');
dash.id = 'god-mode-dashboard';
dash.style.cssText = "margin: 10px 0; padding: 12px; background: #fdfdfd; border: 1px solid #ccc; border-top: 3px solid #007185; border-radius: 4px; display: flex; flex-direction: column; gap: 8px; font-size: 12px; width: 100%; box-sizing: border-box;";
dash.innerHTML = `
${returnHtml}
<div style="display:flex; flex-wrap:wrap; gap:12px; align-items:center;">
${ppuHtml}
<span style="color:#333;">${bsrText}</span>
</div>
<div style="display:flex; flex-wrap:wrap; gap:10px; align-items:center; border-top:1px solid #eee; padding-top:8px;">
<span id="as-copy-link" style="color:#007185; cursor:pointer; font-weight:bold;" title="Copy tracking-free URL">🔗 Copy Clean Link (${asin})</span>
<span style="color:#ccc;">|</span>
<a href="${lensLink}" target="_blank" style="color:#B12704; font-weight:bold; text-decoration:none;" title="Reverse image search on Google to find AliExpress/Dropshippers">🕵️ AliExpress Spotter</a>
<span style="color:#ccc;">|</span>
<a href="https://www.hagglezon.com/en/s/${asin}" target="_blank" style="color:#007185; font-weight:bold; text-decoration:none;">🌍 Compare EU Prices</a>
${warehouseHtml !== '' ? '<span style="color:#ccc;">|</span>' + warehouseHtml : ''}
</div>
`;
if (targetContainer.id === 'titleSection') {
targetContainer.insertAdjacentElement('afterend', dash);
} else {
targetContainer.insertAdjacentElement('afterbegin', dash);
}
document.getElementById('as-copy-link').addEventListener('click', (e) => {
GM_setClipboard(`https://www.amazon.co.uk/dp/${asin}`);
e.target.innerText = '✅ Clean Link Copied!';
setTimeout(() => e.target.innerText = `🔗 Copy Clean Link (${asin})`, 1500);
});
}
// --- PRODUCT PAGE: KEEPA (BOTTOM) ---
function injectKeepa() {
if (!isProductPage() || document.getElementById('as-keepa-box')) return;
const targetContainer = document.querySelector('#feature-bullets') || document.querySelector('#centerCol');
if (!targetContainer) return;
const asinMatch = window.location.href.match(/\/(?:dp|gp\/product)\/([A-Z0-9]{10})/);
const asin = asinMatch ? asinMatch[1] : '';
if (!asin) return;
const box = document.createElement('div');
box.id = 'as-keepa-box';
box.style.cssText = "margin-top: 15px; border: 1px solid #ddd; padding: 15px; background: #fff; text-align: center; width: 100%; box-sizing: border-box; border-radius: 4px;";
box.innerHTML = `
<div style="font-weight:bold; margin-bottom:10px; font-size:13px; color:#0f1111; text-align:left;">📉 Price History (Keepa)</div>
<img id="keepa-graph-img" src="https://graph.keepa.com/pricehistory.png?asin=${asin}&domain=co.uk" style="width:100%; max-width: 600px; cursor:pointer; border-radius:4px; border: 1px solid #eee;">
`;
if (targetContainer.id === 'feature-bullets') {
targetContainer.insertAdjacentElement('afterend', box);
} else {
targetContainer.insertAdjacentElement('beforeend', box);
}
document.getElementById('keepa-graph-img').addEventListener('click', () => {
window.open(`https://keepa.com/#!product/2-${asin}`);
});
}
// --- RELIABLE EVENT LOOP ---
function runEngines() {
try {
if (isProductPage()) {
// On product pages, master OFF removes enhancements; ON injects them.
if (!getFlag('amz_master')) {
const dash = document.getElementById('god-mode-dashboard');
if (dash && dash.parentElement) dash.parentElement.removeChild(dash);
const keepa = document.getElementById('as-keepa-box');
if (keepa && keepa.parentElement) keepa.parentElement.removeChild(keepa);
return;
}
injectProductDashboard();
injectKeepa();
} else {
// Always show sidebar on search pages so the master switch is reachable.
injectSidebar();
applySortAndFilter();
setupSearchResultsWatcher();
scheduleSearchApply(); // catch late DOM (sidebar sometimes before results)
}
} catch (error) {
console.warn("God Mode Script Encountered a DOM Error:", error);
}
}
// Re-apply when Amazon swaps results (pagination, filters, SPA navigation).
hookHistoryForSearchPages();
runEngines();
})();