Greasy Fork

Greasy Fork is available in English.

ヨドバシ検索結果で量あたり単価を表示

Shift+A/Shift+B:量あたり単価上限で絞り込み .:価格上限入力フォームにフォーカス

当前为 2019-09-29 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name ヨドバシ検索結果で量あたり単価を表示
// @description Shift+A/Shift+B:量あたり単価上限で絞り込み .:価格上限入力フォームにフォーカス
// @match *://*.yodobashi.com/*
// @version 0.2.2
// @grant none
// @namespace http://greasyfork.icu/users/181558
// ==/UserScript==

(function() {

  const debug = 0; //Math.random() > 0.8; // trueでデバッグモード1
  const debug2 = 0; //Math.random() > 0.8; // trueでデバッグモード2
  const enableBeta = 1; // 1でポイント還元後価格(想像)を表示

  var gStaY = 0;

  function sta(str, pointer = 0) { // 右下ステータス表示
    return $('<span style="all:initial; ' + (pointer ? 'cursor:pointer; ' : '') + 'position: fixed; right:1em; bottom: ' + (gStaY += 2.5) + 'em; z-index:2147483647; opacity:1; font-size:90%; font-weight:bold; margin:0px 1px; text-decoration:none !important; text-align:center; padding:1px 6px 1px 6px; border-radius:12px; background-color:#6080ff; color:white; white-space: nowrap; ">' + str + '</span>').appendTo(document.body);
  }

  if (debug) sta("debug1");
  if (debug2) sta("debug2");

  var isorder = location.href.match(/https?:\/\/order\./);
  var issearch = location.href.match(/[\?\&]word\=/);
  var iscate = location.href.match(/category\//);
  var isproduct = location.href.match(/\/product\//);
  var parentLimit = isorder ? 5 : 4;

  const titleXPath = '//div[@class="pName"]/p[2]|.//h1[@id="products_maintitle"]/span|.//span[@class="js_c_commodityName"]|.//a[@id="LinkProduct01"]|.//div[@class="product js_productName"]|.//a[@class="js_productListPostTag js-clicklog js-taglog-schRlt"]/p[2]';
  const priceXPath = '//span[@class="productPrice"]|.//span[@id="js_scl_unitPrice"]|.//div[@class="price red"]/strong|.//li[@class="Special"]/em|.//span[@class="red js_ppSalesPrice"]';
  const pointrateXPath = '//span[@class="spNone"]|.//span[@id="js_scl_pointrate"]|.//div[@class="point orange"]|.//li[@class="Point"]|.//span[@class="orange js_ppPoint"]|.//div[@class="pInfo liMt05"]/ul/li/span[@class="orange ml10"]';

  var cppLimit = [0, 0];

  function inputcpplimit(e, type, autonumber = null) {
    e.stopPropagation();
    e.preventDefault();
    var ret = proInput("量あたり価格上限を入力してください", autonumber || cppLimit[type]);
    if (ret === null || ret == cppLimit[type]) return false;
    cppLimit[type] = ret;
    sessionStorage.setItem("cppLimit" + type, cppLimit[type] || "") || 0;
    location.reload();
    return false;
  }
  if (issearch || iscate) {
    cppLimit[1] = sessionStorage.getItem("cppLimit1") || 0;
    if (cppLimit[1]) $(sta("limit1(Shift+A): " + cppLimit[1], 1)).appendTo(document.body).attr("title", "クリックかShift+Aでこの量単価の上限で絞り込む").click(e => inputcpplimit(e, 1));
    cppLimit[2] = sessionStorage.getItem("cppLimit2") || 0;
    if (cppLimit[2]) $(sta("limit2(Shift+B): " + cppLimit[2], 1)).appendTo(document.body).attr("title", "クリックかShift+Bでこの量単価の上限で絞り込む").click(e => inputcpplimit(e, 2));
    document.addEventListener("keydown", function(e) {
        if (/input|textarea/i.test(e.target.tagName)) return;
        var pressed = (e.ctrlKey ? 'c-' : '') + (e.altKey ? 'a-' : '') + (e.shiftKey ? 's-' : '') + String(e.key);
        if (pressed == "s-A") inputcpplimit(e, 1); // shift+a 量あたり価格上限
        if (pressed == "s-B") inputcpplimit(e, 2); // shift+b 量あたり価格上限
      },
      false);
  }

  // .キーで上限絞り込みにフォーカス、全選択状態
  $(document).keypress((e) => {
    if (/input|textarea/i.test(e.target.tagName)) return;
    if (String(e.key) == ".") {
      var ele = eleget0('//input[@id="js_upperPrice"]');
      e.preventDefault();
      if (ele) {
        ele.focus();
        $(ele).select();
        ele.scrollIntoView({ behavior: "smooth", block: "center", inline: "center" });
      }
    }
  });

  $('input#js_upperPrice').css('ime-mode', 'inactive'); // 上限価格はIME offにする

  const niwait = 100; //50;
  setTimeout(() => { run(document); }, niwait);
  document.body.addEventListener('DOMNodeInserted', function(evt) { setTimeout(() => { run(evt.target); }, niwait); }, false);

  function run(node) {
    for (let titleEle of elegeta(titleXPath, node)) with({ ppr: ppr, ppr2: ppr2 }) {

      var rndcolor = '#' + (0x1000000 + (Math.random()) * 0xffffff).toString(16).substr(1, 6);

      var title = titleEle.innerText
      if (titleEle.dataset.yCpP) continue;
      else titleEle.dataset.yCpP = "1";

      debugEle(titleEle, rndcolor);

      var parentEle = titleEle;

      if (issearch || isproduct) { // 「店頭でのみ販売しています|予定数の販売を終了しました|販売を終了しました」を非表示
        for (let ele of elegeta('//div[@class="pInfo"]/ul/li', node)) {
          if (ele.innerText.match(/店頭でのみ販売しています|販売を終了しました/)) { debugRemove(ele.parentNode.parentNode.parentNode.parentNode); continue; }
        }
      }

      for (var i = 0; i < parentLimit; i++) {
        parentEle = parentEle.parentNode;
        let f = elegeta(priceXPath, parentEle).length;
        if (f == 1) break;
        if (f > 1) i = parentLimit + 1;
      }
      if (i > parentLimit) continue;

      debugEle(parentEle, rndcolor);

      if (i == parentLimit) continue;


      if ((issearch || isproduct || iscate) && ((parentEle.parentNode.innerText)).match(/内蔵SSD|内蔵ハードディスク/)) { // SSD、HDDならuserbenchmarkリンク付ける
        var ele = parentEle.parentNode.insertBefore(document.createElement("a"), parentEle.nextSibling);
        var iflsite = (Math.random() > 0.5) ? "https://duckduckgo.com/?q=!ducky+" : "https://www.google.com/webhp?#btnI=I&q=";
        ele.innerHTML += ' <a rel=\"noopener noreferrer nofollow\" href="' + iflsite + titleEle.innerText.split(" ")[0].replace(/\/JP|JP/gm, "") + '%20site:*.userbenchmark.com" style="font-weight:bold; font-size:80%;display:inline-block;margin:1px 3px;  padding:0.03em 0.5em 0.03em 0.5em; border-radius:99px; background-color:#609070; color:white; white-space: nowrap; ">UserBenchmark</a>';
      }

      var priceEle = eleget0(priceXPath, parentEle);
      if (!priceEle) continue;
      debugEle(priceEle, rndcolor);
      var price = Number(priceEle.innerText.match(/\D([0-9\,]+)/)[1].replace(/\,/g, ""));

      var pointEle = eleget0(pointrateXPath, parentEle);
      if (pointEle) {
        var pointtext = pointEle.innerText.replace(/\,/g, "");
        if (pointtext.match(/([0-9]+)(?:%)/)) {
          debugEle(pointEle, rndcolor);
          var pointPer = Number(pointtext.match(/([0-9,]+)(?:%)/)[1] / 100);
        } else
        if (pointtext.match(/([0-9]+)(?:ポイント)/)) {
          debugEle(pointEle, rndcolor);
          var pointPer = Number(pointtext.match(/([0-9]+)(?:ポイント)/)[1] / price);
          /* if (debug) */
          pointEle.innerHTML += "<span style='background-color:#fff8e8;'>(" + Math.round(pointPer * 100) + "%?)</span>";
        }
        if (pointPer) {
          var point = Math.ceil(price - (price * (price / (price + price * pointPer))));
          var pricef = Math.round(price - point).toLocaleString();

          if (enableBeta) priceEle.innerHTML += " <span style='background-color:#fff0f0;'>" + (isorder ? "<br>還元後:" : "(還元後:") + "¥" + pricef + (isorder ? "" : ")") + "</span>";
        }
      } else { var point = -1; }

      // type1
      var pass1 = 0;
      if (title.match(/ゴミ袋|ポリ袋/) || ((isproduct || issearch) && ((parentEle.innerText)).match(/ゴミ袋|ポリ袋/)))
        var ryou = title.replace(/\,/g, "").match(/\D([0-9\.]+)(P)/);
      else
      if (title.match(/ラップ/) || ((isproduct || issearch) && ((parentEle.innerText)).match(/ラップ/)))
        var ryou = title.replace(/\,/g, "").match(/\D([0-9\.]+)(m)/);
      else
      if (title.match(/USBメモリ|(外付け|外付|ポータブル|内蔵|バルク|接続)(SSD|HDD|ハードディスク)|バルクドライブ|2\.5.?(inc|インチ)|7mm|9.5mm/) || ((isproduct || issearch) && ((parentEle.innerText)).match(/(内蔵|外付け|ポータブル)(SSD|HDD|ハードディスク)/)))
        var ryou = title.replace(/\,/g, "").match(/\D([0-9\.]+)(mg|㎎|g|ml|mL|ml|GB)|(?:[^A-Z0-9\.\-])([0-9\.]+)(L|kg|㎏|Kg|TB)/);
      else
        var ryou = title.replace(/\,/g, "").match(/\D([0-9\.]+)(mg|㎎|g|ml|mL|ml)|(?:[^A-Z0-9\.\-])([0-9\.]+)(L|kg|㎏|Kg)/);

      if (ryou && (ryou[1] > 0 || ryou[3] > 0)) {
        if (ryou[4]) ryou[4] = ryou[4].replace(/kg|㎏|Kg/, "g").replace(/L/, "ml").replace(/TB/, "GB");
        var ryout = Number(ryou[1]) || Number(ryou[3]) * 1000;
        var mul = (title.match(/×[0-9\.\,]+/m) && !(title.match(/[\((\[].*×.*[\))\]]/m)) && !(title.match(/×[\d\s]*(cm|mm)/))) ? Number(title.match(/×([0-9\.\,]+)/)[1]) : 1;

        if (point > -1) {
          var ppr = Math.round(100 * (price - point) / Number(ryout * mul)) / 100;

          var ele = $('<span style="all:initial; display:inline-block; font-weight:bold; font-size:90%;margin:0.5px 0px 0.5px 3px; text-decoration:none !important;  padding:0.03em 0.5em 0.03em 0.2em; border-radius:99px; background-color:#6080b0; color:white; white-space: nowrap; ">¥' + ppr + '/' + (ryou[2] || ryou[4]) + '</span>').appendTo(titleEle);
          if ((!isproduct) && (!isorder)) $(ele).css("cursor", "pointer").attr("title", "クリックかShift+Aでこの量単価の上限で絞り込む").click(e => inputcpplimit(e, 1, ppr));
          pass1 = ((iscate || issearch) && cppLimit[1] && ppr <= cppLimit[1]) ? 1 : 0;
        }
      }

      // type2
      var pass2 = 0;
      var ryou1 = ryou;
      var ryou = title.replace(/\,/g, "").match(/\D([0-9\.]+)(枚|粒|錠|包|杯|本|個|袋|組入|ポート|色|日分|ヶ入|食)/);
      if (ryou && (ryou[1] > 0 || ryou[3] > 0)) {
        if (ryou[4]) ryou[4] = ryou[4].replace(/kg|㎏|Kg/, "g").replace(/L/, "ml").replace(/TB/, "GB");
        var ryout = Number(ryou[1]) || Number(ryou[3]) * 1000;
        var mul = 1; //(title.match(/×[0-9\.\,]+/m) && !(title.match(/[\((\[].*×.*[\))\]]/m)) && !(title.match(/×[\d\s]*(cm|mm)/)))?Number(title.match(/×([0-9\.\,]+)/)[1]):1;

        if (point > -1) {
          var ppr2 = Math.round(100 * (price - point) / Number(ryout * mul)) / 100;

          var ele = $('<span style="all:initial; display:inline-block; font-weight:bold; font-size:90%;margin:0.5px 0px 0.5px 3px; text-decoration:none !important;  padding:0.03em 0.5em 0.03em 0.2em; border-radius:99px; background-color:#6080b0; color:white; white-space: nowrap; ">¥' + ppr2 + '/' + (ryou[2] || ryou[4]) + '</span>').appendTo(titleEle);
          if ((!isproduct) && (!isorder)) $(ele).css("cursor", "pointer").attr("title", "クリックかShift+Bでこの量単価の上限で絞り込む").click(e => inputcpplimit(e, 2, ppr2));

          pass2 = ((iscate || issearch) && cppLimit[2] && ppr2 <= cppLimit[2]) ? 1 : 0;
        }
      }

      if ((cppLimit[1] > 0 && pass1 == 0) || (cppLimit[1] > 0 && (!ryou1))) debugRemove(parentEle.parentNode);
      if ((cppLimit[2] > 0 && pass2 == 0) || (cppLimit[2] > 0 && (!ryou))) debugRemove(parentEle.parentNode);
    }
  }

  function elegeta(xpath, node = document) {
    var ele = document.evaluate("." + xpath, node, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
    var array = [];
    for (var i = 0; i < ele.snapshotLength; i++) array[i] = ele.snapshotItem(i);
    return array;
  }

  function eleget0(xpath, node = document) {
    var ele = document.evaluate("." + xpath, node, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
    return ele.snapshotLength > 0 ? ele.snapshotItem(0) : "";
  }

  function proInput(prom, defaultval, min = Number.MIN_SAFE_INTEGER, max = Number.MAX_SAFE_INTEGER) {
    var inp = window.prompt(prom, defaultval);
    if (inp === undefined || inp === null) return inp;
    return Math.min(Math.max(Number(inp.replace(/[A-Za-z0-9.]/g, function(s) { return String.fromCharCode(s.charCodeAt(0) - 65248); }).replace(/[^-^0-9^\.]/g, "")), min), max);
  }

  function debugEle(ele, col) {
    if (debug) {
      ele.style.outline = "3px dotted " + col;
      ele.style.boxShadow = " 0px 0px 4px 4px " + col + "30, inset 0 0 100px " + col + "20"
    }
  }

  function debugRemove(ele) {
    if (debug2) { ele.style.opacity = "0.5"; } else ele.remove();
  }
})()