您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Add price per gram and discount percentage to products
当前为
- // ==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);
- })();