Greasy Fork is available in English.

Amazon - Prix au kilo

Affiche les prix au kilo et tri par prix croissant

  1. // ==UserScript==
  2. // @name Amazon - Prix au kilo
  3. // @name:en Amazon - Price per weight
  4. // @namespace http://tampermonkey.net/
  5. // @version 1.2
  6. // @description Affiche les prix au kilo et tri par prix croissant
  7. // @description:en Display price per weight & sort ascending
  8. // @author Shuunen
  9. // @match https://*.amazon.fr/*
  10. // @grant none
  11. // ==/UserScript==
  12.  
  13. (function() {
  14. "use strict";
  15.  
  16. // Used https://babeljs.io/repl/ to convert this code to es2015
  17.  
  18. var app = {
  19. id: "amz-kg",
  20. processOne: false,
  21. processOnce: false,
  22. hideStuff: false,
  23. showDebug: false,
  24. injectRealPrice: true,
  25. sortProducts: true
  26. };
  27.  
  28. app.debug = app.processOne;
  29.  
  30. var cls = {
  31. handled: app.id + "-handled",
  32. avoided: app.id + "-avoided",
  33. debug: app.id + "-debug",
  34. pricePer: app.id + "-price-per"
  35. };
  36.  
  37. var selectors = {
  38. list: "ul.s-result-list",
  39. item:
  40. ".s-result-item:not(.aok-hidden):not(." +
  41. cls.handled +
  42. "):not(." +
  43. cls.avoided +
  44. ")",
  45. itemTitle: ".s-access-title",
  46. otherPrice: ".a-size-base.a-color-price.s-price.a-text-bold",
  47. pricePer:
  48. ".a-size-base.a-color-price:not(.s-price):not(.a-text-bold)," +
  49. "." +
  50. cls.pricePer,
  51. debugContainer:
  52. ".a-fixed-left-grid-col.a-col-right .a-row:not(.a-spacing-small) .a-column.a-span7",
  53. debug: "." + cls.debug,
  54. pantry: "img.sprPantry",
  55. stuffToHide: ".s-result-item .a-column.a-span7 .a-row:not(." + cls.debug + ")"
  56. };
  57.  
  58. selectors.price = selectors.debugContainer + " div:first-child .a-link-normal";
  59.  
  60. var regex = {
  61. price: /EUR (\d+,\d\d)/i,
  62. weight: /(\d+)\s?(g|-)/i,
  63. bulk: /Lot de (\d+)/i,
  64. pricePer: /EUR (\d+,\d\d)\/(\w+)(\s\w+)?/i
  65. };
  66.  
  67. var templates = {
  68. debug:
  69. '<div class="a-row ' +
  70. cls.debug +
  71. '"><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>',
  72. price: '<span class="s-price a-text-bold">EUR {{price}}</span>',
  73. pricePerKilo:
  74. '<span class="a-color-price s-price a-text-bold ' +
  75. cls.pricePer +
  76. '">EUR {{pricePerKilo}}/kg</span>'
  77. };
  78.  
  79. var products = [];
  80.  
  81. function findOne(selector, context, dontYell) {
  82. context = context || document;
  83. var item = context.querySelector(selector);
  84. if (item && app.debug) {
  85. console.log('found element matching "' + selector + '"');
  86. } else if (!item && !dontYell) {
  87. console.warn('found no element for selector "' + selector + '"');
  88. }
  89. return item;
  90. }
  91.  
  92. function findFirst(selector, context) {
  93. return findAll(selector, context)[0];
  94. }
  95.  
  96. function findAll(selector, context) {
  97. context = context || document;
  98. var items = Array.prototype.slice.call(context.querySelectorAll(selector));
  99. if (items.length && app.debug) {
  100. console.log("found", items.length, 'elements matching "' + selector + '"');
  101. } else if (!items.length) {
  102. console.warn('found no elements for selector "' + selector + '"');
  103. }
  104. return items;
  105. }
  106.  
  107. function shadeBadProducts() {
  108. findAll(selectors.pantry).forEach(function(el) {
  109. var item =
  110. el.parentElement.parentElement.parentElement.parentElement.parentElement
  111. .parentElement.parentElement.parentElement;
  112. item.style.filter = "grayscale(100%)";
  113. item.style.opacity = 0.5;
  114. item.style.order = 1000;
  115. item.classList.add(cls.avoided);
  116. if (app.debug) {
  117. console.log("shaded item", item);
  118. }
  119. });
  120. }
  121.  
  122. function priceStrToFloat(str) {
  123. var price = str.replace(",", ".");
  124. price = parseFloat(price);
  125. return price;
  126. }
  127.  
  128. function priceFloatToStr(num) {
  129. var price = num.toFixed(1);
  130. price = price.replace(".", ",") + "0";
  131. return price;
  132. }
  133.  
  134. function getPrice(text) {
  135. var matches = text.match(regex.price);
  136. if (app.debug) {
  137. console.log("found price matches :", matches);
  138. }
  139. var price = matches && matches.length === 2 ? matches[1] : "0";
  140. price = priceStrToFloat(price);
  141. if (app.debug) {
  142. console.log("found price", price);
  143. }
  144. return price;
  145. }
  146.  
  147. function getWeightAndUnit(text) {
  148. var matches = text.match(regex.weight);
  149. // console.log('found weight matches & unit :', matches)
  150. var data = {
  151. weight: 0,
  152. unit: ""
  153. };
  154. if (matches && matches.length === 3) {
  155. data.weight = matches[1];
  156. data.unit = matches[2];
  157. }
  158. if (data.unit === "-") {
  159. data.unit = "g";
  160. }
  161. // console.log('found weight & unit :', data)
  162. return data;
  163. }
  164.  
  165. function getBulk(text) {
  166. var matches = text.match(regex.bulk);
  167. // console.log('found bulk matches :', matches)
  168. var bulk = matches && matches.length === 2 ? matches[1] : "1";
  169. bulk = parseInt(bulk);
  170. // console.log('found bulk', bulk)
  171. return bulk;
  172. }
  173.  
  174. function getProductDataViaPricePer(text) {
  175. var matches = text.match(regex.pricePer);
  176. // console.log('found pricePer matches :', matches)
  177. var data = {
  178. price: 0,
  179. weight: 0,
  180. unit: "",
  181. bulk: 1
  182. };
  183. if (matches && matches.length === 4) {
  184. data.price = priceStrToFloat(matches[1]);
  185. if (matches[3]) {
  186. data.weight = matches[2];
  187. data.unit = matches[3].trim();
  188. } else {
  189. data.weight = 1;
  190. data.unit = matches[2];
  191. }
  192. }
  193. if (app.debug) {
  194. console.log("found pricePer :", data);
  195. }
  196. return data;
  197. }
  198.  
  199. function getTitle(text) {
  200. return text
  201. .split(" ")
  202. .slice(0, 5)
  203. .join(" ");
  204. }
  205.  
  206. function getProductData(item, data) {
  207. var text = item.textContent;
  208. var weightAndUnit = getWeightAndUnit(text);
  209. data = data || {};
  210. data.price = getPrice(text);
  211. data.weight = weightAndUnit.weight;
  212. data.unit = weightAndUnit.unit;
  213. data.bulk = getBulk(text);
  214. data.title = getTitle(text);
  215. return data;
  216. }
  217.  
  218. function fill(template, data) {
  219. var tpl = template + "";
  220. Object.keys(data).forEach(function(key) {
  221. var str = "{{" + key + "}}";
  222. var val = data[key];
  223. if (key.indexOf("price") > -1 && val > 0) {
  224. val = priceFloatToStr(val);
  225. }
  226. // console.log('looking for', str)
  227. tpl = tpl.replace(new RegExp(str, "gi"), val);
  228. });
  229. return tpl;
  230. }
  231.  
  232. function showDebugData(item, data) {
  233. var debug = findOne(selectors.debug, item, true);
  234. if (!app.showDebug) {
  235. if (debug) {
  236. // if existing debug zone found
  237. debug.style.display = "none";
  238. }
  239. return;
  240. }
  241. var html = fill(templates.debug, data);
  242. // console.log('debug html', html)
  243. if (debug) {
  244. // if existing debug zone found
  245. debug.style.display = "inherit";
  246. debug.outerHTML = html;
  247. return;
  248. }
  249. debug = document.createElement("div");
  250. debug.innerHTML = html;
  251. var container = findOne(selectors.debugContainer, item);
  252. if (container) {
  253. container.append(debug);
  254. } else {
  255. console.error(data.title, ": failed at finding debug container", item);
  256. }
  257. }
  258.  
  259. function getPricePerKilo(data) {
  260. data.pricePerKilo = 0;
  261. if (data.weight === 0) {
  262. return data;
  263. }
  264. var w = data.weight * data.bulk;
  265. if (data.unit === "g") {
  266. data.pricePerKilo = (1000 / w) * data.price;
  267. } else if (data.unit === "kg") {
  268. data.pricePerKilo = w * data.price;
  269. } else {
  270. console.error(data.title, ": unit not handled :", data.unit);
  271. }
  272. if (data.pricePerKilo >= 0) {
  273. data.pricePerKilo = priceStrToFloat(data.pricePerKilo.toFixed(1));
  274. }
  275. if (app.debug) {
  276. console.log("found pricePerKilo :", data);
  277. }
  278. return data;
  279. }
  280.  
  281. function injectRealPrice(item, data) {
  282. if (!app.injectRealPrice) {
  283. return;
  284. }
  285. if (app.debug) {
  286. console.log("injecting real price :", data);
  287. }
  288. var price = findOne(selectors.price, item);
  289. var text = "";
  290. if (data.pricePerKilo > 0) {
  291. text = fill(templates.pricePerKilo, data);
  292. } else if (data.price > 0) {
  293. text = fill(templates.price, data);
  294. }
  295. var pricePer = findOne(selectors.pricePer, item, true);
  296. if (pricePer) {
  297. pricePer.style.display = "none";
  298. }
  299. price.innerHTML = text;
  300. var otherPrice = findOne(selectors.otherPrice, item, true);
  301. if (otherPrice) {
  302. otherPrice.classList.remove("a-color-price", "a-text-bold");
  303. }
  304. }
  305.  
  306. function avoidProduct(item) {
  307. var nbAttr = item.getAttributeNames().length;
  308. if (nbAttr === 5) {
  309. if (app.debug) {
  310. console.warn("detected ad product", item);
  311. }
  312. return true;
  313. }
  314. // all good
  315. return false;
  316. }
  317.  
  318. function augmentProducts() {
  319. findAll(selectors.item).forEach(function(item) {
  320. return augmentProduct(item);
  321. });
  322. }
  323.  
  324. function augmentProduct(item) {
  325. if (avoidProduct(item)) {
  326. return;
  327. }
  328. var pricePer = findOne(selectors.pricePer, item, true);
  329. var data = {};
  330. if (pricePer) {
  331. pricePer.style.display = "inherit";
  332. data = getProductDataViaPricePer(pricePer.textContent);
  333. } else {
  334. data = getProductData(item);
  335. }
  336. data = getPricePerKilo(data);
  337. if (pricePer) {
  338. data = getProductData(item, data);
  339. }
  340. showDebugData(item, data);
  341. injectRealPrice(item, data);
  342. if (app.processOnce) {
  343. item.classList.add(cls.handled);
  344. }
  345. data.el = item;
  346. products.push(data);
  347. }
  348.  
  349. function hideStuff() {
  350. findAll(selectors.stuffToHide).forEach(function(el) {
  351. return (el.style.display = app.hideStuff ? "none" : "inherit");
  352. });
  353. }
  354.  
  355. function sortProducts() {
  356. var list = findOne(selectors.list);
  357. if (!list) {
  358. return console.error("cannot sort without list");
  359. }
  360. list.style.display = "flex";
  361. list.style.flexDirection = "column";
  362. // trick to have products without pricePerKilo at bottom
  363. products.map(function(p) {
  364. return (p.pricePerKilo = p.pricePerKilo || p.price + 1000);
  365. });
  366. // sort by pricePerKilo
  367. products = products.sort(function(a, b) {
  368. return a.pricePerKilo - b.pricePerKilo;
  369. });
  370. products.forEach(function(p, i) {
  371. return (p.el.style.order = i);
  372. });
  373. }
  374.  
  375. function init() {
  376. console.log(app.id, "is starting...");
  377. shadeBadProducts();
  378. if (app.processOne) {
  379. augmentProduct(findFirst(selectors.item));
  380. } else {
  381. augmentProducts();
  382. sortProducts();
  383. }
  384. hideStuff();
  385. console.log(app.id, "processed", products.length, "products");
  386. }
  387.  
  388. init();
  389.  
  390. })();