Amazon - Prix au kilo

Affiche les prix au kilo et tri par prix croissant

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

// ==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-result-list",
	  item:
		".s-result-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-result-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();

})();