您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Automatically generate unit price for applicable items when shopping online.
// ==UserScript== // @name Unit Price Helper // @namespace https://github.com/yzhu27/UnitPriceHelper/ // @version 0.1 // @description Automatically generate unit price for applicable items when shopping online. // @copyright @yzhu27, @Internationale-NCSU, @Lognam-Huang, @MZWANGgg, @IsleZhu // @match http://www.harristeeter.com/* // @include http://www.harristeeter.com/* // @match https://www.harristeeter.com/* // @include https://www.harristeeter.com/* // @match http://www.costco.com/* // @include http://www.costco.com/* // @match https://www.costco.com/* // @include https://www.costco.com/* // @match http://www.target.com/* // @include http://www.target.com/* // @match https://www.target.com/* // @include https://www.target.com/* // @require https://code.jquery.com/jquery-3.6.1.js // @grant none // ==/UserScript== const RULE_SET = { 'https://www.harristeeter.com/p/': { price_label: "data", capacity_label: "span[id=ProductDetails-sellBy-unit]", function: harrisConverter, label_type: 'value', append_function: appendForHarris, website_type: 'static' }, 'https://www.harristeeter.com/pl/': { price_label: "data", capacity_label: "span[class='kds-Text--s text-neutral-more-prominent']", function: harrisConverter, label_type: 'value', append_function: appendForHarris, website_type: 'static' }, 'https://www.harristeeter.com/search': { price_label: "data", capacity_label: "span[class='kds-Text--s text-neutral-more-prominent']", function: harrisConverter, label_type: 'value', append_function: appendForHarris, website_type: 'static' }, 'https://www.costco.com/': { price_label: 'div[class=price]', capacity_label: 'span[class=description]', function: costcoConverter, label_type: 'text', append_function: appendForCostco, website_type: 'static' }, 'https://www.target.com/s': { price_label: "span[data-test=current-price]", capacity_label: "div[class='Truncate-sc-10p6c43-0 dWgRjr']", function: costcoConverter, // target could share the converter with costco. label_type: 'text', append_function: appendForTarget, website_type: 'dynamic' }, 'https://www.wholefoodsmarket.com/search': { price_label: "span[class=regular_price]", capacity_label: "h2[data-testid=product-tile-name]", //function: targetConverter, label_type: 'text', append_function: appendForTarget, website_type: 'dynamic' } }; const TARGET_URL_PREFIX = [ 'https://www.harristeeter.com/p/', 'https://www.harristeeter.com/search', 'https://www.costco.com/', 'https://www.target.com/s', 'https://www.wholefoodsmarket.com/search', 'https://www.harristeeter.com/pl/' ]; const REGEX = { unit : 'gal|g|kg|lbs|lb|fl oz|oz|qt|fl. oz|ml|litter|Litter|l|L', quant : 'ct|pack|count', float : "\\d+\\.?\\d*?(?:\\s*-\\s*\\d+\\.?\\d*?)?" }; (function () { var host = window.location.host.toLowerCase(); window.priceTipEnabled = true; console.log(host) var url = window.location.href.toLowerCase(); for (let i = 0; i < TARGET_URL_PREFIX.length; i++) { if (url.startsWith(TARGET_URL_PREFIX[i])) { if (RULE_SET[TARGET_URL_PREFIX[i]].website_type == 'static') { addListPriceTips(TARGET_URL_PREFIX[i]) } else if (RULE_SET[TARGET_URL_PREFIX[i]].website_type == 'dynamic') { window.addEventListener("wheel", event => { addListPriceTips(TARGET_URL_PREFIX[i]) }) } } } })(); /** * @property {Function}addListPriceTips Acts like an controller. Designated different converter * and append function for 'addTipsHelper()' according to 'url_prefix' * @param {string} url_prefix the unique identifier prefix of target url. * @returns no return value */ function addListPriceTips(url_prefix) { console.log('addListPriceTips_ is called:' + url_prefix); // query didn't work for 'Target' website //console.log(document); var totalPrice = document.querySelectorAll(RULE_SET[url_prefix].price_label); var totalVolumn = document.querySelectorAll(RULE_SET[url_prefix].capacity_label); //console.log(RULE_SET[url_prefix].price_label); //console.log('len: '+totalPrice.length); //console.log('price: ' + totalPrice[0].textContent); //console.log('volume: ', totalVolumn[0].textContent); var labelType = RULE_SET[url_prefix].label_type; var len = totalPrice.length; for (let i = 0; i < len; i++) { if (totalPrice[i] === null || totalVolumn[i] === null) { continue; } if (labelType === 'value') { addTipsHelper(totalPrice[i].value, totalVolumn[i].textContent, RULE_SET[url_prefix].function, RULE_SET[url_prefix].append_function, i); } else if (labelType === 'text') { addTipsHelper(totalPrice[i].textContent, totalVolumn[i].textContent, RULE_SET[url_prefix].function, RULE_SET[url_prefix].append_function, i); } } console.log(len); return len; } /** * @property {Function}addTipsHelper Acts like an executor. Finished the unit calculating and converting according to the given params. * @param {string} price the raw price value extracted from labels * @param {string} title the raw title value extracted from labels. Volumn is matched using regex from title. * @returns no return value */ function addTipsHelper(price, title, func, appendFun, index) { var convertedResult = func(price, title); if (convertedResult != null) { console.log(convertedResult.finalPrice + '/' + convertedResult.finalUnit); appendFun(convertedResult, index); } } function appendForCostco(convertedResult, index) { console.log('unit price:' + convertedResult.finalPrice, 'unit: ' + convertedResult.finalUnit); var priceSpan = "[" + convertedResult.finalPrice + " / " + convertedResult.finalUnit + "]"; document.getElementsByClassName('price')[index].append(priceSpan); } function appendForHarris(convertedResult, index) { var priceSpan = document.createElement('span'); priceSpan.innerHTML = "[" + convertedResult.finalPrice + " / " + convertedResult.finalUnit + "]"; priceSpan.className = 'kds-Price-promotional-dropCaps'; //left border/margin fails to work priceSpan.style = "font-size: 16px; left-margin: 20px"; //following line is originally working //document.getElementsByClassName('kds-Price-promotional kds-Price-promotional--decorated')[index].appendChild(priceSpan); //following is trying to solve discount item issue, still require testing //use the length of testResult to check whether the price/unit is already provided by the website //if it is provided, the length should be 1 - only has finalPrice as the result //otherwise, the length is 2 - finalPrice and finalUnit if (Object.keys(convertedResult).length == 2) { priceSpan.innerHTML = "[$" + convertedResult.finalPrice + " / " + convertedResult.finalUnit + "]"; priceSpan.className = 'kds-Price-promotional-dropCaps'; //left border/margin fails to work priceSpan.style = "font-size: 16px; left-margin: 20px"; //not elegant, but works //use the length of class name to determine whether the item is having discount //if the item is having discount, the length should be 54 //if the item is not having discount, the length should be 83 var insertedTag = document.getElementsByClassName('kds-Price-promotional kds-Price-promotional--decorated')[index]; if (insertedTag.className.length == 54) { insertedTag.appendChild(priceSpan); } else if (insertedTag.className.length == 83) { insertedTag.appendChild(priceSpan); } else { alert("ERROR: not tag to insert span"); } //try to change CSS, this need to use append(), but failed because is regarded as string // var priceSpan = "<span class=\"kds-Price-promotional-dropCaps\">"+testResult.finalPrice+" / "+testResult.finalUnit+"</span>"; // document.getElementsByClassName('kds-Price-promotional kds-Price-promotional--plain kds-Price-promotional--decorated')[0].append(priceSpan); } else { console.log("Price/unit is already provided.") } } function appendForTarget(convertedResult, index) { var priceSpan = "[" + convertedResult.finalPrice + " / " + convertedResult.finalUnit + "]"; if (!document.querySelectorAll('span[data-test=current-price]')[index].textContent.endsWith('.')) { document.querySelectorAll('span[data-test=current-price]')[index].append(priceSpan + '.'); } } function harrisConverter(price, title) { //solve if the price/unit is already provided by the website var itemFinalUnit = ''; if (title[0] == '$') { itemFinalUnit = title; return { finalPrice: itemFinalUnit } } else { //quantity cannot solve 1/2 yet //quantity can already solve 0.5 by yZhu var itemQuantity = title.match(/([1-9]\d*\.?\d*)|(0\.\d*[1-9])/)[0]; //console.log(itemQuantity); //optimize to solve special cases as '20 ct 0.85' var itemUnit = title.match(/\s((([a-zA-Z]*\s?[a-zA-Z]+)*))/)[1]; //console.log(itemUnit); var itemPriceByUnit = parseFloat(price) / parseFloat(itemQuantity); //cut long tails after digit itemPriceByUnit = itemPriceByUnit.toFixed(3); //console.log(itemPriceByUnit); switch (itemUnit) { case 'gal': itemFinalUnit = 'gal'; break; case 'oz': itemFinalUnit = 'oz'; break; case 'fl oz': itemFinalUnit = 'oz'; break; case 'ct': itemFinalUnit = 'count'; break; case 'lb': itemFinalUnit = 'lb'; break; case 'pack': itemFinalUnit = 'pack'; break; case 'pk': itemFinalUnit = 'pack'; break; case 'bottles': itemFinalUnit = 'Bottle'; break; case 'cans': itemFinalUnit = 'can'; break; case 'L': itemFinalUnit = 'L'; break; case 'l': itemFinalUnit = 'L'; break; case 'ml': itemFinalUnit = 'ml'; break; case 'unit': itemFinalUnit = 'unit'; break; case 'box': itemFinalUnit = 'box'; break; case 'boxes': itemFinalUnit = 'box'; break; case 'suit': itemFinalUnit = 'suit'; break; case 'suits': itemFinalUnit = 'suit'; break; case 'bag': itemFinalUnit = 'bag'; break; case 'bags': itemFinalUnit = 'bag'; break; //may be some other units else? default: itemFinalUnit = 'unknown unit'; } if (itemPriceByUnit > 1000 || itemPriceByUnit < 0) { return null; } else { //console.log("Hihi"); return { finalPrice: itemPriceByUnit, finalUnit: itemFinalUnit }; } } } function costcoConverter(price, title) { title = title.trim().toLowerCase(); console.log('title: ' + title); price = parseFloat(price.trim().substring(1)); var regQuant = REGEX.quant; var regUnit = REGEX.unit; var regFloat = REGEX.float; var unitMatcher = new RegExp('(' + regFloat + ')-?\\s*?' + '('+ regUnit + ')'); var quantMatcher = new RegExp('(' + regFloat + ')-?\\s*?' + '('+ regQuant + ')'); var matchQuant = null; var matchUnit = null; matchQuant = quantMatcher.exec(title); matchUnit = unitMatcher.exec(title); var unit = ' '; var count = 'count'; var quant = 1; var capacity = 1; if(matchQuant!=null){ console.log(matchQuant[0]); count = matchQuant[2]; quant = parseFloat(matchQuant[1]); } if(matchUnit!=null){ console.log(matchUnit[0]); unit = matchUnit[2]; capacity = parseFloat(matchUnit[1]); } var unitPrice = parseFloat(price) / (capacity*quant); if(unit === ' ' ){ unit = count; } return { finalUnit: unit, finalPrice: Math.round(unitPrice * 100) / 100, }; } if (typeof process === "object" && typeof require === "function") { module.exports={ addListPriceTips, harrisConverter, costcoConverter, appendForHarris, appendForCostco, appendForTarget }; }
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址