You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

90 lines
4.4KB

  1. // ==UserScript==
  2. // @name [e-dostavka.by, gipermall.by] price comparison
  3. // @namespace http://tampermonkey.net/
  4. // @version 2
  5. // @description Show prices per kilo, compare between sites
  6. // @author nomoreknights
  7. // @match https://e-dostavka.by/*
  8. // @match https://gipermall.by/*
  9. // @grant unsafeWindow
  10. // @grant GM.xmlHttpRequest
  11. // @require https://cdnjs.cloudflare.com/ajax/libs/arrive/2.4.1/arrive.min.js
  12. // @require https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.min.js
  13. // @require https://unpkg.com/ohm-js@0.14.0/dist/ohm.min.js
  14. // ==/UserScript==
  15. ; (function ($) {
  16. 'use strict';
  17. const price = (() => {
  18. const priceGrammar = ohm.grammar(`Price {
  19. Price = integer "р." integer "к." Price?
  20. integer = digit+
  21. }`)
  22. const priceVal = priceGrammar.createSemantics().addOperation('val', {
  23. Price: (rub, _1, kop, _2, _3) => rub.val() + kop.val() * 0.01,
  24. integer: _ => parseInt(_.sourceString)
  25. })
  26. const weightGrammar = ohm.grammar(`Weight {
  27. Exp = BS AnyWeight BS AnyWeight? BS
  28. AnyWeight = MulWeight | Weight
  29. Weight = float (Unit1 | Unit10 | Unit1000) "."*
  30. MulWeight = float ("x" | "х" | "*") Weight
  31. float = digit+ ("." digit+)? -- fl
  32. Unit1 = "г" | "мл"
  33. Unit10 = "см"
  34. Unit1000 = "кг" | "л" | "м" | "шт"
  35. BS = (~AnyWeight any)*
  36. }`)
  37. const weightVal = weightGrammar.createSemantics().addOperation('val', {
  38. Exp: (_1, weight, _2, _3, _4) => weight.val(),
  39. Weight: (n, u, _1) => n.val() * u.val(),
  40. MulWeight: (d, _, w) => d.val() * w.val(),
  41. Unit1: _ => 0.001,
  42. Unit10: _ => 0.01,
  43. Unit1000: _ => 1,
  44. float: _ => parseFloat(_.sourceString)
  45. })
  46. const interpret = (grammar, semantics, text) => {
  47. const match = grammar.match(text)
  48. if (match.failed()) console.log(`Failed to parse [${text}] with ${match.message}`)
  49. return semantics(match).val()
  50. }
  51. return (price, weight) => interpret(priceGrammar, priceVal, price) / interpret(weightGrammar, weightVal, weight)
  52. })()
  53. const template = $("<a/>").css({
  54. 'background-color': '#fbba00',
  55. 'color': 'black', // default is #cb4f2b
  56. 'padding': '2px',
  57. 'border-radius': '5px',
  58. 'font-size': '14px',
  59. 'font-family': 'MyriadProCondencedBoldItalic',
  60. 'position': 'absolute',
  61. 'right': '5px'
  62. }).attr('target', '_blank')
  63. $(document).arrive(".products_card", { existing: true }, div => {
  64. try {
  65. const normalizedPrice = price(
  66. $(div).find('div > form > div.prices__wrapper > div > div.prices_block > div > div.price').text(),
  67. $(div).find('a.fancy_ajax').text()
  68. )
  69. const link = template.clone()
  70. $(div).find('a.fa').replaceWith(link)
  71. link.text(normalizedPrice.toFixed(2))
  72. const url = (R.test(/e-dostavka/, location.host) ? R.identity : R.flip)(R.replace)('e-dostavka', 'gipermall', $(div).find('a.fancy_ajax').attr('href'))
  73. GM.xmlHttpRequest({
  74. method: 'GET', url, onload: page => {
  75. try {
  76. const html = $($.parseHTML(page.responseText))
  77. const competingPrice = price(
  78. html.find('div.services_wrap:nth-child(3) > div:nth-child(1) > div:nth-child(1) > div:nth-child(1)').text(),
  79. html.find('.template_1_columns > h1:nth-child(2)').text()
  80. )
  81. const diff = (1 - competingPrice / normalizedPrice) * 100
  82. link.text((_, text) => `${text} (${diff > 0 ? '>' : '<'}${Math.abs(diff).toFixed(0)}%)`)
  83. .prop('href', url)
  84. } catch (e) { console.log(`Failed to process page ${url}`, e) }
  85. }
  86. });
  87. } catch (e) { console.log(`Failed to process product card with ${$(div).find('a.fancy_ajax').text()}`, e) }
  88. });
  89. })(unsafeWindow.jQuery); // somehow, when @including jquery, infinite scroll stops working.