Albert Heijn Korting

Add price per gram and discount percentage to products

目前為 2024-11-02 提交的版本,檢視 最新版本

// ==UserScript==
// @name         Albert Heijn Korting
// @namespace    https://wol.ph/
// @version      1.0.1
// @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==

const DEBUG = false;

(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;
		if(DEBUG)
			productCards = [document.querySelector('article[data-testhook="product-card"]')];
		else
        	productCards = document.querySelectorAll('article[data-testhook="product-card"]');

        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');
			console.log(`price: ${priceInt} :: ${priceFrac}`);
            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
    if(!DEBUG)setInterval(updateProductInfo, 5000);
})();