Albert Heijn Kortingspercentage en prijs per kilogram

Add price per gram and discount percentage to products

目前為 2024-10-31 提交的版本,檢視 最新版本

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

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

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

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

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

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

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         Albert Heijn Kortingspercentage en prijs per kilogram
// @namespace    https://wol.ph/
// @version      1.0.0
// @description  Add price per gram and discount percentage to products
// @author       wolph
// @match        https://www.ah.nl/*
// @icon         https://icons.duckduckgo.com/ip2/ah.nl.ico
// @grant        none
// @license      BSD
// ==/UserScript==

(function() {
    'use strict';

    // Set to keep track of processed products
    const processedProducts = new Set();

    function updateProductInfo() {
        console.log('Starting updateProductInfo');

        // Handle normal product cards
        let productCards = document.querySelectorAll('.product-card_root__4toFZ');
        console.log(`Found ${productCards.length} product cards`);

        productCards.forEach(function(productCard, index) {
            console.log(`Processing product card ${index}`);

            // Use a unique identifier for the product
            let productId = productCard.getAttribute('data-product-id') || productCard.querySelector('a.link_root__EqRHd')?.getAttribute('href');
            if (!productId) {
                console.log('Product ID not found, skipping');
                return;
            }

            if (processedProducts.has(productId)) {
                console.log(`Product ${productId} already processed, skipping`);
                return;
            }

            console.log(`Processing product ${productId}`);

            // Extract price
            let priceElement = productCard.querySelector('[data-testhook="price-amount"]');
            if (!priceElement) {
                console.log('Price element not found, skipping');
                return;
            }
            let priceInt = priceElement.querySelector('.price-amount_integer__+e2XO');
            let priceFrac = priceElement.querySelector('.price-amount_fractional__kjJ7u');
            if (!priceInt || !priceFrac) {
                console.log('Price integer or fractional part not found, skipping');
                return;
            }
            let price = parseFloat(priceInt.textContent + '.' + priceFrac.textContent);
            console.log(`Price: ${price}`);

            // Extract unit size
            let unitSizeElement = productCard.querySelector('[data-testhook="product-unit-size"]');
            if (!unitSizeElement) {
                console.log('Unit size element not found, skipping');
                return;
            }
            let unitSizeText = unitSizeElement.textContent.trim(); // e.g., "300 g"
            console.log(`Unit size text: ${unitSizeText}`);

            // Parse unit size
            let weightMatch = unitSizeText.match(/([\d.,]+)\s*(g|kg)/i);
            if (!weightMatch) {
                console.log('Weight not found in unit size text, skipping');
                return;
            }
            let weight = parseFloat(weightMatch[1].replace(',', '.'));
            let unit = weightMatch[2].toLowerCase();
            console.log(`Weight: ${weight}, Unit: ${unit}`);

            // Convert weight to grams
            if (unit === 'kg') {
                weight = weight * 1000;
                console.log(`Converted weight to grams: ${weight}`);
            }

            // Calculate price per kg
            let pricePerKg = (price * 1000) / weight; // Price per kg
            console.log(`Price per kg: €${pricePerKg.toFixed(2)}`);

            // Extract old price if available
            let oldPrice = null;
            // Assuming there's an element for the old price
            let oldPriceElement = priceElement.querySelector('.price-amount_original__A6_Ck');
            if (oldPriceElement) {
                let oldPriceInt = oldPriceElement.querySelector('.price-amount_integer__+e2XO');
                let oldPriceFrac = oldPriceElement.querySelector('.price-amount_fractional__kjJ7u');
                if (oldPriceInt && oldPriceFrac) {
                    oldPrice = parseFloat(oldPriceInt.textContent + '.' + oldPriceFrac.textContent);
                    console.log(`Old price: ${oldPrice}`);
                }
            }

            // Calculate discount percentage
            let discountPercentage = null;
            if (oldPrice) {
                discountPercentage = ((oldPrice - price) / oldPrice) * 100;
                discountPercentage = discountPercentage.toFixed(1);
                console.log(`Calculated discount percentage: ${discountPercentage}%`);
            } else {
                // Try extracting from shield
                let shieldElement = productCard.querySelector('[data-testhook="product-shield"]');
                if (shieldElement) {
                    let shieldTextElement = shieldElement.querySelector('.shield_text__kNeiW');
                    if (shieldTextElement) {
                        let shieldText = shieldTextElement.textContent.trim();
                        console.log(`Shield text: ${shieldText}`);
                        discountPercentage = parsePromotionText(shieldText);
                        if (discountPercentage) {
                            console.log(`Extracted discount percentage from shield: ${discountPercentage}%`);
                        }
                    }
                }
            }

            // If discount percentage is not found, set it to 0%
            if (!discountPercentage) {
                discountPercentage = 0;
                console.log('Discount percentage not found, setting to 0%');
            }

            // Add or update shield element with discount percentage
            let shieldElement = productCard.querySelector('[data-testhook="product-shield"]');
            if (!shieldElement) {
                // Create shield element
                console.log('Creating shield element');
                shieldElement = document.createElement('div');
                shieldElement.className = 'shield_root__SmhpN';
                shieldElement.setAttribute('data-testhook', 'product-shield');

                let shieldTextElement = document.createElement('span');
                shieldTextElement.className = 'shield_text__kNeiW';
                shieldElement.appendChild(shieldTextElement);

                let shieldContainer = productCard.querySelector('.product-card-portrait_shieldProperties__+JZJI');
                if (!shieldContainer) {
                    // Create shield container
                    console.log('Creating shield container');
                    shieldContainer = document.createElement('div');
                    shieldContainer.className = 'product-card-portrait_shieldProperties__+JZJI';
                    let header = productCard.querySelector('.header_root__ilMls');
                    header.appendChild(shieldContainer);
                }
                shieldContainer.appendChild(shieldElement);
            }

            // Update shield text with discount percentage
            let shieldTextElement = shieldElement.querySelector('.shield_text__kNeiW');
            if (!shieldTextElement) {
                shieldTextElement = document.createElement('span');
                shieldTextElement.className = 'shield_text__kNeiW';
                shieldElement.appendChild(shieldTextElement);
            }
            // Just set the text to the discount percentage
            shieldTextElement.textContent = `${discountPercentage}%`;

            // Set background and text color based on discount percentage
            let { backgroundColor, textColor } = getDiscountColors(discountPercentage);
            shieldElement.style.backgroundColor = backgroundColor;
            shieldElement.style.color = textColor; // Ensure text is readable

            // Modify price element to include price per kg in small letters next to discounted price and old price
            let priceContainer = priceElement.parentElement; // Should be '.price_portrait__pcgwD'
            if (priceContainer) {
                // Create or update price per kg element
                let pricePerKgElement = priceContainer.querySelector('.price-per-kg');
                if (!pricePerKgElement) {
                    pricePerKgElement = document.createElement('div');
                    pricePerKgElement.className = 'price-per-kg';
                    pricePerKgElement.style.fontSize = 'smaller';
                    priceContainer.appendChild(pricePerKgElement);
                }
                pricePerKgElement.textContent = `€${pricePerKg.toFixed(2)} per kg`;
            }

            // Mark this product as processed
            processedProducts.add(productId);
            console.log(`Product ${productId} processed`);
        });

        // Handle promotion cards
        let promotionCards = document.querySelectorAll('.promotion-card_root__ENX4w');
        console.log(`Found ${promotionCards.length} promotion cards`);

        promotionCards.forEach(function(promotionCard, index) {
            console.log(`Processing promotion card ${index}`);

            // Use a unique identifier for the promotion
            let promotionId = promotionCard.getAttribute('id') || promotionCard.querySelector('a').getAttribute('href');
            if (!promotionId) {
                console.log('Promotion ID not found, skipping');
                return;
            }

            if (processedProducts.has(promotionId)) {
                console.log(`Promotion ${promotionId} already processed, skipping`);
                return;
            }

            console.log(`Processing promotion ${promotionId}`);

            // Extract current price and previous price
            let priceElement = promotionCard.querySelector('[data-testhook="price"]');
            if (!priceElement) {
                console.log('Price element not found, skipping');
                return;
            }
            let priceNow = parseFloat(priceElement.getAttribute('data-testpricenow'));
            let priceWas = parseFloat(priceElement.getAttribute('data-testpricewas'));
            console.log(`Price now: ${priceNow}, Price was: ${priceWas}`);

            // Extract unit size, if available
            let weight = null;
            let unit = null;

            // Try to extract from description and title
            let cardDescription = promotionCard.querySelector('[data-testhook="card-description"]');
            let cardTitle = promotionCard.querySelector('[data-testhook="card-title"]');
            let descriptionText = cardDescription?.textContent.trim() || '';
            let titleText = cardTitle?.textContent.trim() || '';

            console.log(`Description text: ${descriptionText}`);
            console.log(`Title text: ${titleText}`);

            // Try to extract weight from description or title
            let weightMatch = descriptionText.match(/([\d.,]+)\s*(g|kg)/i) || titleText.match(/([\d.,]+)\s*(g|kg)/i);
            if (weightMatch) {
                weight = parseFloat(weightMatch[1].replace(',', '.'));
                unit = weightMatch[2].toLowerCase();
                if (unit === 'kg') {
                    weight = weight * 1000;
                }
                console.log(`Weight: ${weight} grams`);
            } else {
                console.log('Weight not found in description or title');
            }

            // Calculate discount percentage
            let discountPercentage = null;
            if (priceWas && priceNow) {
                discountPercentage = ((priceWas - priceNow) / priceWas) * 100;
                discountPercentage = discountPercentage.toFixed(1);
                console.log(`Calculated discount percentage: ${discountPercentage}%`);
            } else {
                // Try extracting from promotion shield
                let promotionShields = promotionCard.querySelector('[data-testhook="promotion-shields"]');
                if (promotionShields) {
                    let shieldText = promotionShields.textContent.trim();
                    console.log(`Promotion shield text: ${shieldText}`);
                    discountPercentage = parsePromotionText(shieldText);
                    if (discountPercentage) {
                        console.log(`Extracted discount percentage from shield: ${discountPercentage}%`);
                    }
                }
            }

            if (!discountPercentage) {
                discountPercentage = 0;
                console.log('Discount percentage not found, setting to 0%');
            }

            // Add or update promotion shield with discount percentage
            let promotionShields = promotionCard.querySelector('[data-testhook="promotion-shields"]');
            if (!promotionShields) {
                // Create promotion shields element
                console.log('Creating promotion shields element');
                promotionShields = document.createElement('div');
                promotionShields.className = 'promotion-shields_root__cVEfN';
                promotionShields.setAttribute('data-testhook', 'promotion-shields');
                promotionShields.style = promotionCard.querySelector('.promotion-card-content_root__A5Fda')?.style || '';

                let cardContent = promotionCard.querySelector('.promotion-card-content_root__A5Fda');
                cardContent.insertBefore(promotionShields, cardContent.firstChild);
            }

            // Update promotion shield text with discount percentage
            // Replace the entire element with discount percentage
            promotionShields.innerHTML = ''; // Clear existing content

            let shieldP = document.createElement('p');
            shieldP.className = 'typography_root__Om3Wh typography_variant-paragraph__T5ZAU typography_hasMargin__4EaQi promotion-shield_root__mIDdK';
            shieldP.setAttribute('data-testhook', 'promotion-shield');

            let promotionShieldText = document.createElement('span');
            promotionShieldText.className = 'promotion-text_root__1sn7K promotion-text_large__lTZOA';
            promotionShieldText.setAttribute('data-testhook', 'promotion-text');

            // Set the discount percentage as text
            promotionShieldText.textContent = `${discountPercentage}%`;

            // Set background and text color based on discount percentage
            let { backgroundColor, textColor } = getDiscountColors(discountPercentage * 1.5);
            shieldP.style.backgroundColor = backgroundColor;
            shieldP.style.color = textColor; // Ensure text is readable

            shieldP.appendChild(promotionShieldText);
            promotionShields.appendChild(shieldP);

            // If weight is available, calculate price per kg
            if (weight) {
                let pricePerKg = (priceNow * 1000) / weight;
                console.log(`Price per kg: €${pricePerKg.toFixed(2)}`);

                // Modify price element to include price per kg in small letters next to discounted price and old price
                let priceContainer = priceElement; // Assuming this is the correct container
                if (priceContainer) {
                    // Create or update price per kg element
                    let pricePerKgElement = priceContainer.querySelector('.price-per-kg');
                    if (!pricePerKgElement) {
                        pricePerKgElement = document.createElement('div');
                        pricePerKgElement.className = 'price-per-kg';
                        pricePerKgElement.style.fontSize = 'smaller';
                        pricePerKgElement.style.marginTop = '4px';
                        priceContainer.appendChild(pricePerKgElement);
                    }
                    pricePerKgElement.textContent = `€${pricePerKg.toFixed(2)} per kg`;
                }
            }

            // Mark this promotion as processed
            processedProducts.add(promotionId);
            console.log(`Promotion ${promotionId} processed`);
        });

        console.log('Finished updateProductInfo');
    }

    function parsePromotionText(shieldText) {
        let discountPercentage = null;
        if (shieldText.includes('%')) {
            // e.g., "25% korting"
            let discountMatch = shieldText.match(/(\d+)%\s*korting/i);
            if (discountMatch) {
                discountPercentage = parseFloat(discountMatch[1]);
            }
        } else if (shieldText.toLowerCase().includes('gratis')) {
            if (shieldText.includes('1+1') || shieldText.includes('1 + 1') || shieldText.includes('1+1 gratis')) {
                discountPercentage = 50;
            } else if (shieldText.includes('2+1') || shieldText.includes('2 + 1')) {
                discountPercentage = 33.33;
            }
            // Add more cases as necessary
        } else if (shieldText.includes('2e halve prijs')) {
            discountPercentage = 25; // Assuming 25% off on 2 items
        }
        return discountPercentage;
    }

    function getDiscountColors(discountPercentage) {
        // Use predefined colors and text colors based on discount percentage ranges
        let backgroundColor, textColor;

        if (discountPercentage >= 80) {
            backgroundColor = '#008000'; // Dark Green
            textColor = '#FFFFFF'; // White
        } else if (discountPercentage >= 60) {
            backgroundColor = '#32CD32'; // Lime Green
            textColor = '#000000'; // Black
        } else if (discountPercentage >= 40) {
            backgroundColor = '#FFFF00'; // Yellow
            textColor = '#000000'; // Black
        } else if (discountPercentage >= 20) {
            backgroundColor = '#FFA500'; // Orange
            textColor = '#000000'; // Black
        } else {
            backgroundColor = '#FF0000'; // Red
            textColor = '#FFFFFF'; // White
        }

        return { backgroundColor, textColor };
    }

    // Run immediately
	window.setTimeout(updateProductInfo, 1000);
    // Run the update function every 5 seconds
    setInterval(updateProductInfo, 5000);
})();