carrefour per unit price sorter

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

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

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

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 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();
})();