diff --git a/e-dostavka.user.js b/e-dostavka.user.js new file mode 100644 index 0000000..4852cca --- /dev/null +++ b/e-dostavka.user.js @@ -0,0 +1,90 @@ +// ==UserScript== +// @name [e-dostavka.by, gipermall.by] price comparison +// @namespace http://tampermonkey.net/ +// @version 2 +// @description Show prices per kilo, compare between sites +// @author nomoreknights +// @match https://e-dostavka.by/* +// @match https://gipermall.by/* +// @grant unsafeWindow +// @grant GM.xmlHttpRequest +// @require https://cdnjs.cloudflare.com/ajax/libs/arrive/2.4.1/arrive.min.js +// @require https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.min.js +// @require https://unpkg.com/ohm-js@0.14.0/dist/ohm.min.js +// ==/UserScript== + +; (function ($) { + 'use strict'; + const price = (() => { + const priceGrammar = ohm.grammar(`Price { + Price = integer "р." integer "к." Price? + integer = digit+ + }`) + const priceVal = priceGrammar.createSemantics().addOperation('val', { + Price: (rub, _1, kop, _2, _3) => rub.val() + kop.val() * 0.01, + integer: _ => parseInt(_.sourceString) + }) + const weightGrammar = ohm.grammar(`Weight { + Exp = BS AnyWeight BS AnyWeight? BS + AnyWeight = MulWeight | Weight + Weight = float (Unit1 | Unit10 | Unit1000) "."* + MulWeight = float ("x" | "х" | "*") Weight + float = digit+ ("." digit+)? -- fl + Unit1 = "г" | "мл" + Unit10 = "см" + Unit1000 = "кг" | "л" | "м" | "шт" + BS = (~AnyWeight any)* + }`) + const weightVal = weightGrammar.createSemantics().addOperation('val', { + Exp: (_1, weight, _2, _3, _4) => weight.val(), + Weight: (n, u, _1) => n.val() * u.val(), + MulWeight: (d, _, w) => d.val() * w.val(), + Unit1: _ => 0.001, + Unit10: _ => 0.01, + Unit1000: _ => 1, + float: _ => parseFloat(_.sourceString) + }) + const interpret = (grammar, semantics, text) => { + const match = grammar.match(text) + if (match.failed()) console.log(`Failed to parse [${text}] with ${match.message}`) + return semantics(match).val() + } + return (price, weight) => interpret(priceGrammar, priceVal, price) / interpret(weightGrammar, weightVal, weight) + })() + const template = $("").css({ + 'background-color': '#fbba00', + 'color': 'black', // default is #cb4f2b + 'padding': '2px', + 'border-radius': '5px', + 'font-size': '14px', + 'font-family': 'MyriadProCondencedBoldItalic', + 'position': 'absolute', + 'right': '5px' + }).attr('target', '_blank') + $(document).arrive(".products_card", { existing: true }, div => { + try { + const normalizedPrice = price( + $(div).find('div > form > div.prices__wrapper > div > div.prices_block > div > div.price').text(), + $(div).find('a.fancy_ajax').text() + ) + const link = template.clone() + $(div).find('a.fa').replaceWith(link) + link.text(normalizedPrice.toFixed(2)) + const url = (R.test(/e-dostavka/, location.host) ? R.identity : R.flip)(R.replace)('e-dostavka', 'gipermall', $(div).find('a.fancy_ajax').attr('href')) + GM.xmlHttpRequest({ + method: 'GET', url, onload: page => { + try { + const html = $($.parseHTML(page.responseText)) + const competingPrice = price( + html.find('div.services_wrap:nth-child(3) > div:nth-child(1) > div:nth-child(1) > div:nth-child(1)').text(), + html.find('.template_1_columns > h1:nth-child(2)').text() + ) + const diff = (1 - competingPrice / normalizedPrice) * 100 + link.text((_, text) => `${text} (${diff > 0 ? '>' : '<'}${Math.abs(diff).toFixed(0)}%)`) + .prop('href', url) + } catch (e) { console.log(`Failed to process page ${url}`, e) } + } + }); + } catch (e) { console.log(`Failed to process product card with ${$(div).find('a.fancy_ajax').text()}`, e) } + }); +})(unsafeWindow.jQuery); // somehow, when @including jquery, infinite scroll stops working. \ No newline at end of file