Albert Heijn Korting

Add price per gram and discount percentage to products

当前为 2024-11-02 提交的版本,查看 最新版本

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==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);
})();