Only shows the products sold and delivered by eMag
// ==UserScript==
// @name eMAG Cleaner
// @name:ro eMAG Curățător
// @name:bg eMAG Почистващ
// @name:hu eMAG Tisztító
// @description Only shows the products sold and delivered by eMag
// @description:ro Afișează doar produsele vândute și livrate de eMAG
// @description:bg Показва само продуктите, продавани и доставяни от eMAG
// @description:hu Csak az eMAG által értékesített és szállított termékeket jeleníti meg
// @author NWP + scumpisor
// @namespace https://greasyfork.org/users/877912
// @version 1.0.0
// @license MIT
// @match *://*.emag.ro/*
// @match *://*.emag.hu/*
// @match *://*.emag.bg/*
// @grant GM_addStyle
// @grant GM_getValue
// @grant GM_setValue
// @grant unsafeWindow
// ==/UserScript==
const DEBUG = false;
const log = (...args) => DEBUG && console.log('[eMag Filter]', ...args);
log('Script loaded on:', window.location.href);
// unsafeWindow is the real page window — required because @grant isolates this
// script in a sandbox where window.fetch and EM are not the page's own globals.
const pageWindow = unsafeWindow;
// --- i18n ---
const STRINGS = {
en: {
title: '🔧 eMAG Cleaner',
subtitle: 'Configure filtering options:',
filterThird: 'Filter third-party products',
filterPromo: 'Filter promoted products',
expand: 'Expand',
collapse: 'Collapse',
},
ro: {
title: '🔧 eMAG Curățător',
subtitle: 'Configurează opțiunile de filtrare:',
filterThird: 'Filtrează produsele vândute de terți',
filterPromo: 'Filtrează produsele promovate',
expand: 'Extinde',
collapse: 'Restrânge',
},
bg: {
title: '🔧 eMAG Почистващ',
subtitle: 'Конфигурирайте опциите за филтриране:',
filterThird: 'Филтрирай продукти от трети страни',
filterPromo: 'Филтрирай промотирани продукти',
expand: 'Разгъни',
collapse: 'Свий',
},
hu: {
title: '🔧 eMAG Tisztító',
subtitle: 'Szűrési beállítások konfigurálása:',
filterThird: 'Harmadik féltől származó termékek szűrése',
filterPromo: 'Hirdetett termékek szűrése',
expand: 'Kinyitás',
collapse: 'Bezárás',
},
};
function detectLang() {
const raw = (navigator.language || navigator.userLanguage || 'en').toLowerCase().split('-')[0];
return STRINGS[raw] ? raw : 'en';
}
const lang = detectLang();
const t = STRINGS[lang];
log('Detected language:', lang);
// --- State ---
let filterThirdParty = GM_getValue('filterThirdParty', true);
let filterPromoted = GM_getValue('filterPromoted', true);
let collapsed = GM_getValue('collapsed', false);
// Product id → item map built from API/page globals
let emMap = null;
log('Restored state — filterThirdParty:', filterThirdParty, '| filterPromoted:', filterPromoted, '| collapsed:', collapsed);
// --- Fetch interceptor -------------------------------------------------------
// Must patch pageWindow.fetch (the real page fetch), not the sandboxed window.fetch.
// Without unsafeWindow this patch was silently a no-op and API data was never captured.
const originalFetch = pageWindow.fetch;
pageWindow.fetch = function (...args) {
const url = args[0];
if (typeof url === 'string' && url.includes('/search-by-url')) {
return originalFetch.apply(this, args).then(response => {
response.clone().json().then(data => {
if (data?.data?.items) {
buildMap(data.data.items);
log('Built emMap from API response, size:', emMap.size);
applyFilters();
}
}).catch(() => {});
return response;
});
}
return originalFetch.apply(this, args);
};
// --- Page globals ------------------------------------------------------------
// EM.listingGlobals is set inline by eMAG's own page scripts. Must read it via
// pageWindow — accessing bare `EM` from the sandbox would always be undefined.
function tryPageGlobals() {
if (typeof pageWindow.EM !== 'undefined' && pageWindow.EM?.listingGlobals?.items) {
buildMap(pageWindow.EM.listingGlobals.items);
log('Built emMap from page globals, size:', emMap.size);
}
}
function buildMap(items) {
emMap = new Map(items.map(item => [item.id, item]));
}
// --- Styles ---
GM_addStyle(`
#emag-cleaner-panel {
position: fixed;
bottom: 2.25rem;
right: 2.25rem;
z-index: 999999;
background: linear-gradient(135deg, #6a5acd 0%, #4a90d9 100%);
border-radius: 1.5rem;
padding: 1.65rem 1.65rem 1.35rem 1.65rem;
width: 25.5rem;
box-shadow: 0 0.75rem 3rem rgba(0,0,0,0.35);
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
font-size: 1.5rem;
color: white;
user-select: none;
box-sizing: border-box;
}
#emag-cleaner-panel h3 {
margin: 0;
font-size: 1.425rem;
font-weight: 700;
display: flex;
align-items: center;
justify-content: center;
gap: 0.6rem;
white-space: nowrap;
cursor: pointer;
border-radius: 0.6rem;
padding: 0.2rem 0.3rem;
transition: background 0.2s;
}
#emag-cleaner-panel h3:hover {
background: rgba(255,255,255,0.1);
}
#emag-cleaner-collapse-btn {
background: rgba(255,255,255,0.3);
border: 0.2rem solid rgba(255,255,255,0.6);
border-radius: 0.6rem;
color: white;
font-size: 1.05rem;
font-weight: 900;
line-height: 1;
width: 2.4rem;
height: 2.4rem;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
transition: background 0.2s, transform 0.3s;
padding: 0;
pointer-events: none;
cursor: pointer;
}
#emag-cleaner-collapse-btn.collapsed {
transform: rotate(180deg);
}
#emag-cleaner-body {
overflow: hidden;
transition: max-height 0.3s ease, opacity 0.3s ease, margin-top 0.3s ease;
max-height: 18rem;
opacity: 1;
margin-top: 1.35rem;
}
#emag-cleaner-body.collapsed {
max-height: 0;
opacity: 0;
margin-top: 0;
}
#emag-cleaner-panel .subtitle {
font-size: 1.08rem;
text-align: center;
opacity: 0.85;
margin-bottom: 1.35rem;
}
#emag-cleaner-panel .toggle-row {
background: #1e1e2e;
border-radius: 1.125rem;
padding: 1.2rem 1.35rem;
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 0.75rem;
font-size: 1.125rem;
font-weight: 500;
cursor: pointer;
transition: background 0.2s;
}
#emag-cleaner-panel .toggle-row:hover {
background: #2a2a3e;
}
#emag-cleaner-panel .toggle-row:last-child {
margin-bottom: 0;
}
#emag-cleaner-panel .toggle {
position: relative;
width: 3.9rem;
height: 2.25rem;
flex-shrink: 0;
margin-left: 0.9rem;
}
#emag-cleaner-panel .toggle input {
opacity: 0;
width: 0;
height: 0;
}
#emag-cleaner-panel .slider {
position: absolute;
inset: 0;
background: #555;
border-radius: 2.25rem;
transition: background 0.25s;
cursor: pointer;
}
#emag-cleaner-panel .slider:before {
content: '';
position: absolute;
width: 1.65rem;
height: 1.65rem;
left: 0.3rem;
top: 0.3rem;
background: white;
border-radius: 50%;
transition: transform 0.25s;
}
#emag-cleaner-panel .toggle input:checked + .slider {
background: #22c55e;
}
#emag-cleaner-panel .toggle input:checked + .slider:before {
transform: translateX(1.65rem);
}
`);
// --- DOM ---
const panel = document.createElement('div');
panel.id = 'emag-cleaner-panel';
panel.innerHTML = `
<h3 id="emag-cleaner-header">
<button id="emag-cleaner-collapse-btn" title="${collapsed ? t.expand : t.collapse}">▼</button>
<span>${t.title}</span>
</h3>
<div id="emag-cleaner-body">
<div class="subtitle">${t.subtitle}</div>
<label class="toggle-row">
<span>${t.filterThird}</span>
<div class="toggle">
<input type="checkbox" id="toggle-third-party" ${filterThirdParty ? 'checked' : ''}>
<span class="slider"></span>
</div>
</label>
<label class="toggle-row">
<span>${t.filterPromo}</span>
<div class="toggle">
<input type="checkbox" id="toggle-promoted" ${filterPromoted ? 'checked' : ''}>
<span class="slider"></span>
</div>
</label>
</div>
`;
document.body.appendChild(panel);
const header = document.getElementById('emag-cleaner-header');
const collapseBtn = document.getElementById('emag-cleaner-collapse-btn');
const body = document.getElementById('emag-cleaner-body');
// Apply restored collapsed state without transition
body.style.transition = 'none';
collapseBtn.style.transition = 'none';
if (collapsed) {
body.classList.add('collapsed');
collapseBtn.classList.add('collapsed');
}
requestAnimationFrame(() => {
body.style.transition = '';
collapseBtn.style.transition = '';
});
function toggleCollapse() {
collapsed = !collapsed;
GM_setValue('collapsed', collapsed);
log('Saved collapsed:', collapsed);
body.classList.toggle('collapsed', collapsed);
collapseBtn.classList.toggle('collapsed', collapsed);
collapseBtn.title = collapsed ? t.expand : t.collapse;
}
header.addEventListener('click', toggleCollapse);
document.getElementById('toggle-third-party').addEventListener('change', e => {
filterThirdParty = e.target.checked;
GM_setValue('filterThirdParty', filterThirdParty);
log('Saved filterThirdParty:', filterThirdParty);
applyFilters();
});
document.getElementById('toggle-promoted').addEventListener('change', e => {
filterPromoted = e.target.checked;
GM_setValue('filterPromoted', filterPromoted);
log('Saved filterPromoted:', filterPromoted);
applyFilters();
});
// --- Core logic --------------------------------------------------------------
function getProductId(card) {
const directId = card.getAttribute('data-product-id');
if (directId) return parseInt(directId);
try {
const raw = card.querySelector('button.add-to-favorites')
.getAttribute('data-product')
.replace(/"/g, '"');
return parseInt(JSON.parse(raw).productid);
} catch (_) {
return null;
}
}
function getVendor(card) {
// Primary: emMap from API/page globals — works in both grid and list view
if (emMap) {
const id = getProductId(card);
if (id !== null) {
const vendor = emMap.get(id)?.offer?.vendor?.name?.default;
if (vendor) return vendor;
}
}
// Fallback: vendor link only rendered in list view
const vendorLink = card.querySelector('.card-vendor a');
if (vendorLink) return vendorLink.textContent.trim();
return null; // unknown — don't hide
}
function isPromoted(card) {
return !!card.querySelector('span.badge.bg-light.bg-opacity-90');
}
function applyFilters() {
const cards = document.querySelectorAll('.card-item.card-standard.js-product-data');
log(`Filtering: found ${cards.length} card(s).`);
let hidden = 0;
cards.forEach(card => {
card.style.display = '';
let hide = false;
if (filterThirdParty) {
const vendor = getVendor(card);
if (vendor && vendor !== 'eMAG') hide = true;
}
if (!hide && filterPromoted && isPromoted(card)) {
hide = true;
}
if (hide) {
card.style.display = 'none';
hidden++;
}
});
log(`Hid ${hidden} of ${cards.length} cards.`);
}
// --- Init ---
tryPageGlobals();
applyFilters();
const observer = new MutationObserver(() => applyFilters());
observer.observe(document.body, { childList: true, subtree: true });
log('MutationObserver started.');