Greasy Fork is available in English.
Affiche les prix au kilo et tri par prix croissant
// ==UserScript== // @name Amazon - Prix au kilo // @name:en Amazon - Price per weight // @namespace http://tampermonkey.net/ // @version 1.2 // @description Affiche les prix au kilo et tri par prix croissant // @description:en Display price per weight & sort ascending // @author Shuunen // @match https://*.amazon.fr/* // @grant none // ==/UserScript== (function() { "use strict"; // Used https://babeljs.io/repl/ to convert this code to es2015 var app = { id: "amz-kg", processOne: false, processOnce: false, hideStuff: false, showDebug: false, injectRealPrice: true, sortProducts: true }; app.debug = app.processOne; var cls = { handled: app.id + "-handled", avoided: app.id + "-avoided", debug: app.id + "-debug", pricePer: app.id + "-price-per" }; var selectors = { list: "ul.s-r###lt-list", item: ".s-r###lt-item:not(.aok-hidden):not(." + cls.handled + "):not(." + cls.avoided + ")", itemTitle: ".s-access-title", otherPrice: ".a-size-base.a-color-price.s-price.a-text-bold", pricePer: ".a-size-base.a-color-price:not(.s-price):not(.a-text-bold)," + "." + cls.pricePer, debugContainer: ".a-fixed-left-grid-col.a-col-right .a-row:not(.a-spacing-small) .a-column.a-span7", debug: "." + cls.debug, pantry: "img.sprPantry", stuffToHide: ".s-r###lt-item .a-column.a-span7 .a-row:not(." + cls.debug + ")" }; selectors.price = selectors.debugContainer + " div:first-child .a-link-normal"; var regex = { price: /EUR (\d+,\d\d)/i, weight: /(\d+)\s?(g|-)/i, bulk: /Lot de (\d+)/i, pricePer: /EUR (\d+,\d\d)\/(\w+)(\s\w+)?/i }; var templates = { debug: '<div class="a-row ' + cls.debug + '"><div class="a-column a-span12">\n <p class="a-spacing-micro">Price : {{price}} \u20AC</p>\n <p class="a-spacing-micro">Weight : {{weight}} {{unit}}</p> \n <p class="a-spacing-small">Bulk : {{bulk}}</p>\n <p class="a-spacing-micro a-size-base a-color-price s-price a-text-bold">P/Kg : {{pricePerKilo}} \u20AC/kg</p>\n </div></div>', price: '<span class="s-price a-text-bold">EUR {{price}}</span>', pricePerKilo: '<span class="a-color-price s-price a-text-bold ' + cls.pricePer + '">EUR {{pricePerKilo}}/kg</span>' }; var products = []; function findOne(selector, context, dontYell) { context = context || document; var item = context.querySelector(selector); if (item && app.debug) { console.log('found element matching "' + selector + '"'); } else if (!item && !dontYell) { console.warn('found no element for selector "' + selector + '"'); } return item; } function findFirst(selector, context) { return findAll(selector, context)[0]; } function findAll(selector, context) { context = context || document; var items = Array.prototype.slice.call(context.querySelectorAll(selector)); if (items.length && app.debug) { console.log("found", items.length, 'elements matching "' + selector + '"'); } else if (!items.length) { console.warn('found no elements for selector "' + selector + '"'); } return items; } function shadeBadProducts() { findAll(selectors.pantry).forEach(function(el) { var item = el.parentElement.parentElement.parentElement.parentElement.parentElement .parentElement.parentElement.parentElement; item.style.filter = "grayscale(100%)"; item.style.opacity = 0.5; item.style.order = 1000; item.classList.add(cls.avoided); if (app.debug) { console.log("shaded item", item); } }); } function priceStrToFloat(str) { var price = str.replace(",", "."); price = parseFloat(price); return price; } function priceFloatToStr(num) { var price = num.toFixed(1); price = price.replace(".", ",") + "0"; return price; } function getPrice(text) { var matches = text.match(regex.price); if (app.debug) { console.log("found price matches :", matches); } var price = matches && matches.length === 2 ? matches[1] : "0"; price = priceStrToFloat(price); if (app.debug) { console.log("found price", price); } return price; } function getWeightAndUnit(text) { var matches = text.match(regex.weight); // console.log('found weight matches & unit :', matches) var data = { weight: 0, unit: "" }; if (matches && matches.length === 3) { data.weight = matches[1]; data.unit = matches[2]; } if (data.unit === "-") { data.unit = "g"; } // console.log('found weight & unit :', data) return data; } function getBulk(text) { var matches = text.match(regex.bulk); // console.log('found bulk matches :', matches) var bulk = matches && matches.length === 2 ? matches[1] : "1"; bulk = parseInt(bulk); // console.log('found bulk', bulk) return bulk; } function getProductDataViaPricePer(text) { var matches = text.match(regex.pricePer); // console.log('found pricePer matches :', matches) var data = { price: 0, weight: 0, unit: "", bulk: 1 }; if (matches && matches.length === 4) { data.price = priceStrToFloat(matches[1]); if (matches[3]) { data.weight = matches[2]; data.unit = matches[3].trim(); } else { data.weight = 1; data.unit = matches[2]; } } if (app.debug) { console.log("found pricePer :", data); } return data; } function getTitle(text) { return text .split(" ") .slice(0, 5) .join(" "); } function getProductData(item, data) { var text = item.textContent; var weightAndUnit = getWeightAndUnit(text); data = data || {}; data.price = getPrice(text); data.weight = weightAndUnit.weight; data.unit = weightAndUnit.unit; data.bulk = getBulk(text); data.title = getTitle(text); return data; } function fill(template, data) { var tpl = template + ""; Object.keys(data).forEach(function(key) { var str = "{{" + key + "}}"; var val = data[key]; if (key.indexOf("price") > -1 && val > 0) { val = priceFloatToStr(val); } // console.log('looking for', str) tpl = tpl.replace(new RegExp(str, "gi"), val); }); return tpl; } function showDebugData(item, data) { var debug = findOne(selectors.debug, item, true); if (!app.showDebug) { if (debug) { // if existing debug zone found debug.style.display = "none"; } return; } var html = fill(templates.debug, data); // console.log('debug html', html) if (debug) { // if existing debug zone found debug.style.display = "inherit"; debug.outerHTML = html; return; } debug = document.createElement("div"); debug.innerHTML = html; var container = findOne(selectors.debugContainer, item); if (container) { container.append(debug); } else { console.error(data.title, ": failed at finding debug container", item); } } function getPricePerKilo(data) { data.pricePerKilo = 0; if (data.weight === 0) { return data; } var w = data.weight * data.bulk; if (data.unit === "g") { data.pricePerKilo = (1000 / w) * data.price; } else if (data.unit === "kg") { data.pricePerKilo = w * data.price; } else { console.error(data.title, ": unit not handled :", data.unit); } if (data.pricePerKilo >= 0) { data.pricePerKilo = priceStrToFloat(data.pricePerKilo.toFixed(1)); } if (app.debug) { console.log("found pricePerKilo :", data); } return data; } function injectRealPrice(item, data) { if (!app.injectRealPrice) { return; } if (app.debug) { console.log("injecting real price :", data); } var price = findOne(selectors.price, item); var text = ""; if (data.pricePerKilo > 0) { text = fill(templates.pricePerKilo, data); } else if (data.price > 0) { text = fill(templates.price, data); } var pricePer = findOne(selectors.pricePer, item, true); if (pricePer) { pricePer.style.display = "none"; } price.innerHTML = text; var otherPrice = findOne(selectors.otherPrice, item, true); if (otherPrice) { otherPrice.classList.remove("a-color-price", "a-text-bold"); } } function avoidProduct(item) { var nbAttr = item.getAttributeNames().length; if (nbAttr === 5) { if (app.debug) { console.warn("detected ad product", item); } return true; } // all good return false; } function augmentProducts() { findAll(selectors.item).forEach(function(item) { return augmentProduct(item); }); } function augmentProduct(item) { if (avoidProduct(item)) { return; } var pricePer = findOne(selectors.pricePer, item, true); var data = {}; if (pricePer) { pricePer.style.display = "inherit"; data = getProductDataViaPricePer(pricePer.textContent); } else { data = getProductData(item); } data = getPricePerKilo(data); if (pricePer) { data = getProductData(item, data); } showDebugData(item, data); injectRealPrice(item, data); if (app.processOnce) { item.classList.add(cls.handled); } data.el = item; products.push(data); } function hideStuff() { findAll(selectors.stuffToHide).forEach(function(el) { return (el.style.display = app.hideStuff ? "none" : "inherit"); }); } function sortProducts() { var list = findOne(selectors.list); if (!list) { return console.error("cannot sort without list"); } list.style.display = "flex"; list.style.flexDirection = "column"; // trick to have products without pricePerKilo at bottom products.map(function(p) { return (p.pricePerKilo = p.pricePerKilo || p.price + 1000); }); // sort by pricePerKilo products = products.sort(function(a, b) { return a.pricePerKilo - b.pricePerKilo; }); products.forEach(function(p, i) { return (p.el.style.order = i); }); } function init() { console.log(app.id, "is starting..."); shadeBadProducts(); if (app.processOne) { augmentProduct(findFirst(selectors.item)); } else { augmentProducts(); sortProducts(); } hideStuff(); console.log(app.id, "processed", products.length, "products"); } init(); })();