carrefour per unit price sorter

Sorts items by price per unit (e.g. `per kg` or `per litre`) in Carrefour online shops in Poland.

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name           carrefour per unit price sorter
// @name:pl        carrefour sortowanie po cenie jednostki
// @namespace      cprn
// @license        GPLv3
// @match          https://*.carrefour.pl/*
// @grant          none
// @version        0.3
// @esversion      11
// @author         cprn
// @run-at         document-idle
// @date           2025-04-26 17:17:26
// @description    Sorts items by price per unit (e.g. `per kg` or `per litre`) in Carrefour online shops in Poland.
// @description:pl Sortuje produkty po cenie za jednostkę (np. za `kg` albo `l`) w sklepie internetowym polskiego Carrefoura.
// ==/UserScript==

// vim copy to clipboard:
// map ,r :wa<cr>gg"+yGzz

(function() {
    'use strict';

    const DEBUG = 1;

    function discoverClasses() {
        let priceTag = null;
        ['zł/1 l', 'zł/1 kg'].every(s => {
            let xpath = `//p[contains(., '${s}')]`;
            priceTag = document.evaluate(xpath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
            return priceTag === null;
        });
        if (!priceTag) {
            console.error("Could not find priceTag");
            return null;
        }
        return {
            item: priceTag.parentNode.parentNode.parentNode.parentNode.classList[0],
            perUnitStrip: priceTag.parentNode.classList[0],
            mass: priceTag.parentNode.children[0].classList[0],
            perMass: priceTag.parentNode.previousSibling.children[0].children[0].classList[0],
        };
    }

    function perUnitPrice(e, classes) {
        let perUnit = e.getElementsByClassName(classes.perUnitStrip)[0]?.lastChild;
        let perMass = e.getElementsByClassName(classes.perMass)[0];
        let mass = e.getElementsByClassName(classes.mass)[0]?.lastChild;

        if (!perUnit || !mass) {
            console.debug('unknown price', e);
            return Infinity; // Treat unknowns as very expensive
        } else if (mass.innerText == '1 l' || mass.innerText == '1 kg') {
            var price = ''.concat(perMass.children[0].innerText, '.', perMass.children[1].innerText);
        } else {
            var price = perUnit.innerText.slice(0, -7).replace(',', '.');
        }

        return parseFloat(price);
    }

    function sortItems() {
        const classes = discoverClasses();
        if (!classes) {
            console.error("cprn: no classes found");
            return;
        }

        const items = Array.from(document.getElementsByClassName(classes.item));
        const parent = items[0]?.parentNode;
        if (!parent) {
            console.error("cprn: no parent found");
            return;
        }

        DEBUG && console.debug('cprn: first item', items[0])
        DEBUG && console.debug('cprn: items container', items[0].parentNode)
        DEBUG && console.debug('cprn: classes\n--------------------\n' +
            'item: ' + classes.item, document.getElementsByClassName(classes.item)[0], '\n' +
            'perUnitStrip: ' + classes.perUnitStrip, document.getElementsByClassName(classes.perUnitStrip)[0], '\n' +
            'mass: ' + classes.mass, document.getElementsByClassName(classes.mass)[0], '\n' +
            'perMass: ' + classes.perMass, document.getElementsByClassName(classes.perMass)[0], '\n' +
            'perUnit: ', perUnitPrice(items[0], classes )
        );

        console.log('cprn: solving world hunger...')
        items.sort((a, b) => perUnitPrice(a, classes) - perUnitPrice(b, classes));
        items.forEach(item => parent.appendChild(item));
        console.log('cprn: done')
    }

    function waitForPage() {
        const check = setInterval(() => {
            if (document.readyState === 'complete') {
                clearInterval(check);
                setTimeout(sortItems, 1000);
            }
        }, 300);
    }

    waitForPage();
})();