|
|
@@ -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 = $("<a/>").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. |