Instacart SI Units

Standardize Instacart units to SI.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name        Instacart SI Units
// @namespace   nikhilweee
// @match       https://www.instacart.com/*
// @grant       none
// @version     1.0
// @author      nikhilweee
// @description Standardize Instacart units to SI.
// @icon        https://www.instacart.com/favicon.ico
// ==/UserScript==

(function () {
    "use strict";

    const CONFIG = {
        units: [
            {
                // Mass
                regex: /(\d+(?:\.\d+)?)\s*(?:lbs?|pounds?)/gi,
                factor: 0.453592,
                unit: "kg"
            },
            {
                regex: /(\d+(?:\.\d+)?)\s*(?:oz|ounces?)/gi,
                factor: 28.3495,
                unit: "g"
            },
            // Length
            {
                regex: /(\d+(?:\.\d+)?)\s*(?:in|inches?|")/gi,
                factor: 2.54,
                unit: "cm"
            },
            {
                regex: /(\d+(?:\.\d+)?)\s*(?:ft|feet|')/gi,
                factor: 0.3048,
                unit: "m"
            },
            {
                regex: /(\d+(?:\.\d+)?)\s*(?:mi|miles?)/gi,
                factor: 1.60934,
                unit: "km"
            },
            // Volume (common in grocery)
            {
                regex: /(\d+(?:\.\d+)?)\s*(?:gal|gallons?)/gi,
                factor: 3.78541,
                unit: "L"
            },
            {
                regex: /(\d+(?:\.\d+)?)\s*(?:fl\s*oz|fluid\s*ounces?)/gi,
                factor: 29.5735,
                unit: "mL"
            }
        ]
    };

    const SIUnits = {
        init() {
            this.observe();
            // Initial process with a slight delay to allow framework to render
            setTimeout(() => this.process(document.body), 1000);
        },

        process(root) {
            if (!root) return;

            const walker = document.createTreeWalker(
                root,
                NodeFilter.SHOW_TEXT,
                {
                    acceptNode: (node) => {
                        // Skip script, style, etc.
                        const tag = node.parentNode.tagName;
                        if (['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'CODE', 'PRE'].includes(tag)) {
                            return NodeFilter.FILTER_REJECT;
                        }
                        if (node.textContent.trim().length === 0) {
                            return NodeFilter.FILTER_REJECT;
                        }
                        // Avoid double processing
                        if (node.parentNode.dataset.siProcessed) {
                            return NodeFilter.FILTER_REJECT;
                        }
                        return NodeFilter.FILTER_ACCEPT;
                    }
                }
            );

            const nodes = [];
            while (walker.nextNode()) {
                nodes.push(walker.currentNode);
            }

            nodes.forEach(node => this.convertNode(node));
        },

        convertNode(node) {
            let text = node.textContent;
            let changed = false;

            CONFIG.units.forEach(u => {
                text = text.replace(u.regex, (match, val, offset, string) => {
                    // Check if already followed by the conversion
                    const nextPart = string.slice(offset + match.length);
                    if (nextPart.trim().startsWith(`(${u.unit}`)) return match;

                    changed = true;
                    const num = parseFloat(val);
                    // Smart formatting: if > 1000g, use kg, etc? For now, stick to direct conversion
                    // Maybe round to reasonable decimals
                    let converted = num * u.factor;

                    // Formatting logic
                    if (u.unit === 'g' && converted >= 1000) {
                        converted = converted / 1000;
                        return `${match} (${converted.toFixed(2).replace(/\.00$/, '')} kg)`;
                    }
                    if (u.unit === 'mL' && converted >= 1000) {
                        converted = converted / 1000;
                        return `${match} (${converted.toFixed(2).replace(/\.00$/, '')} L)`;
                    }

                    return `${match} (${converted.toFixed(2).replace(/\.00$/, '')} ${u.unit})`;
                });
            });

            if (changed) {
                node.textContent = text;
            }
        },

        observe() {
            const mo = new MutationObserver((mutations) => {
                for (const m of mutations) {
                    m.addedNodes.forEach(n => {
                        if (n.nodeType === Node.ELEMENT_NODE) {
                            this.process(n);
                        } else if (n.nodeType === Node.TEXT_NODE) {
                            this.convertNode(n);
                        }
                    });
                }
            });
            mo.observe(document.body, { childList: true, subtree: true });
        }
    };

    SIUnits.init();
})();