eMAG Cleaner

Only shows the products sold and delivered by eMag

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(Tôi đã có Trình quản lý tập lệnh người dùng, hãy cài đặt nó!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==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(/&quot;/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.');