eMAG Cleaner

Only shows the products sold and delivered by eMag

Bu betiği kurabilmeniz için Tampermonkey, Greasemonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği yüklemek için Tampermonkey gibi bir uzantı yüklemeniz gerekir.

Bu betiği kurabilmeniz için Tampermonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği kurabilmeniz için Tampermonkey ya da Userscripts gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği indirebilmeniz için ayrıca Tampermonkey gibi bir eklenti kurmanız gerekmektedir.

Bu komut dosyasını yüklemek için bir kullanıcı komut dosyası yöneticisi uzantısı yüklemeniz gerekecek.

(Zaten bir kullanıcı komut dosyası yöneticim var, kurmama izin verin!)

Bu stili yüklemek için Stylus gibi bir uzantı yüklemeniz gerekir.

Bu stili yüklemek için Stylus gibi bir uzantı kurmanız gerekir.

Bu stili yükleyebilmek için Stylus gibi bir uzantı yüklemeniz gerekir.

Bu stili yüklemek için bir kullanıcı stili yöneticisi uzantısı yüklemeniz gerekir.

Bu stili yüklemek için bir kullanıcı stili yöneticisi uzantısı kurmanız gerekir.

Bu stili yükleyebilmek için bir kullanıcı stili yöneticisi uzantısı yüklemeniz gerekir.

(Zateb bir user-style yöneticim var, yükleyeyim!)

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