Greasy Fork

Greasy Fork is available in English.

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

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

当前为 2021-07-09 提交的版本,查看 最新版本

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name ヨドバシ検索結果で量あたり単価を表示
// @description Shift+A/Shift+B:量単価上限で絞り込み A:単価昇順 .:価格上限入力フォームにフォーカス
// @match *://www.yodobashi.com/*
// @match https://order.yodobashi.com/yc/shoppingcart/index.html*
// @match https://order.yodobashi.com/yc/shoppingcart/action.html*
// @match *://kakaku.com/*
// @match *://search.kakaku.com/*
// @version 0.2.10
// @grant none
// @namespace http://greasyfork.icu/users/181558
// @run-at document-idle
// ==/UserScript==

(function() {
  if (typeof $ === "undefined") { window.addEventListener('DOMContentLoaded', function() { run0(); return; }) } else { run0(); }

  function run0() {

    if (window != parent) return;
    const ddg_google_ratio = 0.0; // 0-1
    const debug = 0; //Math.random() > 0.8; // trueで要素の枠表示
    const debug2 = 0; //Math.random() > 0.8; // trueで半透明に消す
    const enableBeta = 1; // 1でポイント還元後価格(想像)を表示

    var gStaY = 0;

    // -----------------------------------------------------

    // 上が優先
    const SITEINFO = [{
        is: 'KAKAKU',
        urlRE: '//kakaku.com/|//search.kakaku.com/',
        titleXPath: '//p[contains(@class,"p-item_name s-biggerlinkHover_underline")]|//h2[@itemprop="name"]|.//span[@class="name"]',
        priceXPath: '//span[@class="c-num p-item_price_num"]|.//span[@id="minPrice"]/a/span|.//p[@class="p-item_price"]/span|.//span[@class="itemCatPrice"]',
        orderPL: 5,
        listPL: 5,
        isorder: /$^/,
        iscate: /$^/,
        isproduct: /$^/,
        issearch: /search_results|\/\/search\./,
        sortData: [
          { sortPpr: '//span[@class="ppr"]', sortElement: '/../../../../..', },
          //          { sortPpr: '//span[@class="ppr2"]', sortElement: '/../../..', },
        ],
        sortUrl: /https:\/\/kakaku.com\/search_results\//,
        onLoad: () => {},
        onEachItem: (node, titleEle, rndcolor) => {},
        pointFunc: (parentEle, price, priceEle, rndcolor) => {},
        hide: (title, isorder, isproduct, issearch, parentEle, price, iscate, cppLimit, point, titleEle) => {
          // type1
          var pass1 = 0;
          var ryou = title.replace(/\,/g, "").match(/\D([0-9\.]+)(mg|㎎|g|ml|mL|枚|粒|錠|包|杯|本|個|袋|ml|組入|ポート|色)|(?:[^A-Z0-9\.\-])([0-9\.]+)(L|kg|㎏|Kg)/); // /\D([0-9\.]+)\s?(mg|g|ml|mL|mcg)|\D([0-9\.]+)(L|kg)/);
          //          let discreteUnit = /×([0-9\.]+)\s?(セット|Pack|枚|タブレット|カプセル|個|錠|ベジタリアンカプセル|ベジタブルカプセル|植物性カプセル|Veggie Caps|Veg Capsules|ベジカプセル|べジカプセル|カプセル|粒|ベジキャップ|チュアブル錠|ソフトゼリー|ティーバック|ティーバッグ|袋|Softgels|ソフトジェル|Chewable Tablets|錠|Tablets|グミ|ロゼンジ|植物性液体フィトカプセル|Vegetarian Capsules)/m;
          let discreteUnit = /×([0-9\.]+).*?(セット|Pack|枚|タブレット|カプセル|個|錠|ベジタリアンカプセル|ベジタブルカプセル|植物性カプセル|Veggie Caps|Veg Capsules|ベジカプセル|べジカプセル|カプセル|粒|ベジキャップ|チュアブル錠|ソフトゼリー|ティーバック|ティーバッグ|袋|Softgels|ソフトジェル|Chewable Tablets|錠|Tablets|グミ|ロゼンジ|植物性液体フィトカプセル|Vegetarian Capsules)/m;

          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(discreteUnit)) ? Number(title.match(discreteUnit)[1]) : 1;
            var ppr = Math.round(100 * (price) / Number(ryout * mul)) / 100;
            var pprEmbed = (price) / Number(ryout * mul);

            titleEle.style.display = "inline";
            var ele = $('<span class="ppr" data-ppr="' + pprEmbed + '" style="all:initial; position:relative; z-index:999; 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]) + (ppr == undefined ? ryou + "," + ryout + "," + mul : "") + '</span>').insertAfter(titleEle);
            if ((!isproduct) && (!isorder)) setClick(ele, ppr, "\\" + (Math.round(price)) + "/" + Number(ryout * mul) + (ryou[2] || ryou[4]) + "=" + pprEmbed + "\n");
            pass1 = ((iscate || issearch) && cppLimit[1] && ppr <= cppLimit[1] && ryou && ppr) ? 1 : 0;
          }
          // type2
          var pass2 = 0;
          var ryou1 = ryou;
          var ryou = title.match(discreteUnit);
          if (ryou && (ryou[1] > 0)) {
            var ryout = Number(ryou[1]);
            var mul = 1;
            var ppr2 = Math.round(100 * (price) / Number(ryout * mul)) / 100;
            titleEle.style.display = "inline";
            var ele = $('<span style="all:initial; position:relative; z-index:999; 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>').insertAfter(titleEle.parentNode);
            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] && ryou && ppr) ? 1 : 0;
          }
          if (eleget0('//span[contains(@class,"p-switch_btn p-switch_btn-current")]/img[@alt="リスト形式"]')) {
            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);
          } else {
            if ((cppLimit[1] > 0 && pass1 == 0) || (cppLimit[1] > 0 && (!ryou1))) debugRemove(parentEle.parentNode.parentNode.parentNode.parentNode.parentNode);
            if ((cppLimit[2] > 0 && pass2 == 0) || (cppLimit[2] > 0 && (!ryou))) debugRemove(parentEle.parentNode.parentNode.parentNode.parentNode.parentNode);
          }
        },
      },

      // -----------------------------------------------------
      {
        is: 'AMAZON',
        urlRE: 'amazon.co',
        titleXPath: '//a[@class="absolute-link product-link"]|.//section[3]/div[@id="product-summary-header"]/h1',
        priceXPath: '//span[1]/span/span[contains(@class,"a-price-whole")]|.//span[@id="priceblock_ourprice"]|.//span[@class="a-color-price"]|.//span[contains(@class,"p13n-sc-price")]|.//span[contains(@class,"a-color-price")]|.//div/div/div[last()]/span[@class="a-color-base"]/span[contains(text(),"¥")]/..',
        pointrateXPath: '//span[@class="a-size-base a-color-price" and @dir="auto" and contains(text(),"ポイント(")]|.//span[@class="a-color-price" and contains(text(),"pt")]', //'//div[2]/div[1]/span[@class="a-size-base a-color-price"]',
        domNIDelay: 1500,
        orderPL: 5,
        listPL: 5,
        isorder: /https?:\/\/order\./,
        iscate: /category\//,
        isproduct: /\/dp\//,
        issearch: /amazon.*?s\?k=|amazon.*?[\?\&]keywords\=|amazon.*?[\?\&]field-keywords\=/,
        onLoad: () => {},
        onEachItem: (node, titleEle, rndcolor) => {},
        //      pointFunc: (parentEle, price, priceEle, rndcolor) => {},
        //      hide: (title, isorder, isproduct, issearch, parentEle, price, iscate, cppLimit, point, titleEle) => {},
        pointFunc: (parentEle, price, priceEle, rndcolor) => {
          var point = 0;
          var pointEle = eleget0(SITE.pointrateXPath, parentEle);
          if (pointEle) {
            var pointtext = pointEle.innerText.replace(/\d+ポイント|\,/g, "");
            if (pointtext.match(/([0-9]+)\%/)) {
              debugEle(pointEle, rndcolor);
              var pointPer = Number(pointtext.match(/([0-9,]+)%/)[1] / 100);
            }
            if (pointPer) {
              //              var point = Math.ceil(price - (price * (price / (price + price * pointPer))));
              var point = Number(price * pointPer);
              var pricef = Math.round(price - point);

              if (enableBeta) priceEle.innerHTML += " <span style='background-color:#fff0f0;font-size:50%;'>" + "(還元後:" + "¥" + pricef.toLocaleString() + ")</span>";
            }
          }
          return point;
        },
        onRun: (node, cppLimit) => {
          $('.s-line-clamp-4').css({ '-webkit-line-clamp': '9' });
          var titleParentlevelXpathSet = [
            { e: '//h2[@class="a-size-mini a-spacing-none a-color-base s-line-clamp-4"]/a[@class="a-link-normal a-text-normal"]/span[@class="a-size-base-plus a-color-base a-text-normal"]', p: 4 }, // 第1選択 キーワード検索結果のタイトル
            { e: '//a[@class="a-link-normal"]/div[contains(@class,"p13n-sc-truncated") and @aria-hidden="true" and @data-truncate-by-character="1"]', p: 5 }, // この商品をチェックした人はこんな商品もチェックしていますのタイトル
            { e: '//span[@id="productTitle"]', p: 4 },
            { e: '//div[@class="sg-col-inner"]/div/h2[@class="a-size-mini a-spacing-none a-color-base s-line-clamp-2"]/a[@class="a-link-normal a-text-normal"]/span[@class="a-size-medium a-color-base a-text-normal"]', p: 7 },
            { e: '//div[@class="sponsored-products-truncator-truncated"]', p: 3 },
            { e: '//div[@class="p13n-sc-truncated"]', p: 6 },
            { e: '//div[@class="sg-col-inner"]/div[1]/h5/a/span', p: 11 },
            { e: '//h2/a[@class="a-link-normal a-text-normal cleaned"]/span', p: 6 },
          ]
          let titleEleA = [];
          for (let tp of titleParentlevelXpathSet) {
            for (let titleEle of elegeta(tp.e, document)) {
              if (titleEle.dataset.appm == "c") { continue; } else { titleEle.dataset.appm = "c"; }
              var rndcolor = '#' + (0x1000000 + (Math.random()) * 0xffffff).toString(16).substr(1, 6);
              debugEle(titleEle, rndcolor, tp.e);
              var title = num(titleEle.innerText);
              var parentEle = titleEle;
              for (var i = 0; i < tp.p; i++) { parentEle = parentEle.parentNode; if (eleget0(SITE.priceXPath, parentEle)) break; }
              if (i == 10) continue;
              debugEle(parentEle, rndcolor);
              var pass1 = 0;
              var ryou = title.replace(/\,/gm, "").match(/\D([0-9\.]+)(mg|g|G|ml|mL|枚|粒|錠|包|杯|本|個|袋|ml|GB|ペア|組|P|日分|ヶ入|組入|ポート|色|日分|ヶ入|食|巻|入|セット|缶|函)|(?:[^A-Z0-9\.\-])([0-9\.]+)(L|kg|㎏|Kg|kg|キロ|TB)/);
              if (title.match(/クッキングシート/)) var ryou = title.replace(/\,/gm, "").match(/\D([0-9\.]+)(m|g|G|枚|粒|錠|包|杯|本|個|袋|GB|ペア|組|P|日分|ヶ入|組入|ポート|色|日分|ヶ入|食|巻|入|セット|缶|函)|(?:[^A-Z0-9\.\-])([0-9\.]+)(L|kg|㎏|Kg|kg|キロ|TB)/);
              if (ryou) {
                if (ryou[4]) { ryou[4] = ryou[4].replace(/kg|㎏|Kg|kg|キロ/, "g").replace(/L/, "ml").replace(/TB/, "GB"); }
                var ryout = Number(ryou[1]) || Number(ryou[3]) * 1000;
                if (title.match(/×[0-9\.\,]+/m) && !(title.match(/[\((\[].*×.*[\))\]]/m)) && !(title.match(/×[\d\s]*(cm|mm)/))) { ryout *= Number(title.match(/×([0-9\.\,]+)/)[1]); }
                var mul = 1; //title.match(/×([0-9\.\,]+)(個|袋|】)/); mul=mul?mul[1]:1;
                var priceEle = eleget0(SITE.priceXPath, parentEle);
                if (priceEle) {
                  debugEle(priceEle, rndcolor);
                  var price = Number(priceEle.innerText.replace(/[^0-9]/g, ""));
                  var point = SITE.pointFunc(parentEle, price, priceEle, rndcolor);
                  var ppr = Math.round(100 * (price - point) / Number(ryout * mul)) / 100;
                  var ele = $('<span style="all:initial; position:relative; z-index:999; 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>').insertAfter(titleEle.parentNode);
                  if ((!isproduct) && (!isorder)) setClick(ele, ppr);
                }
              }
              if (cppLimit[1] > 0 && (!priceEle || !ryou || (ppr && cppLimit[1] < ppr))) debugRemove(parentEle.parentNode.parentNode.parentNode.parentNode);
            }
          }
        },
      },

      // -----------------------------------------------------

      {
        is: 'IHERB',
        urlRE: 'jp.iherb.com/',
        titleXPath: '//a[@class="absolute-link product-link"]|.//section[3]/div[@id="product-summary-header"]/h1',
        priceXPath: '//span[@class="price "]/bdi|.//div[1]/span[@class="price discount-red"]/bdi|.//div[@id="price"]|.//div[1]/span[@class="price discount-green"]/bdi|//div[@id="price"]',
        //        priceXPath: '//div[contains(@class,"product-price text-nowrap")]/span[1]/bdi|.//div[@id="price"]',
        orderPL: 5,
        listPL: 5,
        isorder: /https?:\/\/order\./,
        iscate: /category\//,
        isproduct: /\/pr\//,
        issearch: /search/,
        onLoad: () => {},
        onEachItem: (node, titleEle, rndcolor) => {},
        pointFunc: (parentEle, price, priceEle, rndcolor) => {},
        hide: (title, isorder, isproduct, issearch, parentEle, price, iscate, cppLimit, point, titleEle) => {
          //          console.log(isorder, issearch, iscate, isproduct);
          // type1
          var pass1 = 0;
          var ryou = title.replace(/\,/g, "").match(/\D([0-9\.]+)\s?(mg|g|ml|mL|mcg)|\D([0-9\.]+)(L|kg)/);
          let discreteUnit = /\D([0-9\.]+)\s?(Pack|枚|タブレット|カプセル|個|錠|ベジタリアンカプセル|ベジタブルカプセル|植物性カプセル|Vegan.*Caps|Softgel.*Capsule|ベジ.*キャップ|ジェルキャップ|Veggie Caps|液.*ジェル|ソフトゲル|ビーガンタブレット|ゼラチンカプセル|ベジソフトジェル|Veg Capsules|ベジカプセル|べジカプセル|カプセル|粒|ベジキャップ|チュアブル錠|ソフトゼリー|ティーバック|ティーバッグ|袋|Softgels|ソフトジェル|Chewable Tablets|錠|Tablets|グミ|ロゼンジ|植物性液体フィトカプセル|Vegetarian Capsules)/m;

          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(discreteUnit)) ? Number(title.match(discreteUnit)[1]) : 1;
            var ppr = Math.round(100 * (price) / Number(ryout * mul)) / 100;
            titleEle.style.display = "inline";
            var ele = $('<span style="all:initial; position:relative; z-index:999; 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]) + (ppr == undefined ? ryou + "," + ryout + "," + mul : "") + '</span>').insertAfter(titleEle.parentNode);
            if ((!isproduct) && (!isorder)) setClick(ele, ppr, "\\" + (Math.round(price)) + "/" + Number(ryout * mul) + (ryou[2] || ryou[4]) + "=" + pprEmbed + "\n");
            pass1 = ((iscate || issearch) && cppLimit[1] && ppr <= cppLimit[1]) ? 1 : 0;
          }
          // type2
          var pass2 = 0;
          var ryou1 = ryou;
          var ryou = title.match(discreteUnit);
          if (ryou && (ryou[1] > 0)) {
            var ryout = Number(ryou[1]);
            var mul = 1;
            var ppr2 = Math.round(100 * (price) / Number(ryout * mul)) / 100;
            titleEle.style.display = "inline";
            var ele = $('<span style="all:initial; position:relative; z-index:999; 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>').insertAfter(titleEle.parentNode);
            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);
        },
      },

      // -----------------------------------------------------

      {
        is: 'YODOBASHI',
        urlRE: '//.*.yodobashi.com/',
        titleXPath: '//div[contains(@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]',
        priceXPath: '//span[@class="productPrice"]|.//span[@id="js_scl_unitPrice"]|.//div[@class="price red"]/strong|.//li[@class="Special"]/em|.//span[@class="red js_ppSalesPrice"]',
        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"]',
        isorder: /https?:\/\/order\./,
        issearch: /[\?\&]word\=/,
        iscate: /category\//,
        isproduct: /\/product\//,
        orderPL: 5,
        listPL: 3,
        sortData: [
          { sortPpr: '//span[@class="ppr2"]', sortElement: '/../../..', },
          { sortPpr: '//span[@class="ppr"]', sortElement: '/../../..', },
        ],
        sortUrl: /https:\/\/www.yodobashi.com\/category\/|https:\/\/www.yodobashi.com\/\?word=/, // todo: |https:\/\/www.yodobashi.com\/maker\/
        //        sortUrl: /https:\/\/www.yodobashi.com\/category\/|https:\/\/www.yodobashi.com\/\?word=|https:\/\/www.yodobashi.com\/maker\//,
        onLoad: () => { //window.addEventListener('DOMContentLoaded', function(){
          // .キーで上限絞り込みにフォーカス、全選択状態
          $(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にする
          //      })
        },
        onEachItem: (node, titleEle, rndcolor) => {
          if (issearch || isproduct) { // 「店頭でのみ販売しています|予定数の販売を終了しました|販売を終了しました」を非表示
            for (let ele of elegeta('//div[@class="pInfo"]/ul/li', node)) {
              if (ele.innerText.match(/店頭でのみ販売しています|販売を終了しました/)) { debugRemove(ele.parentNode.parentNode.parentNode); continue; }
            }
          };
          for (let site of [
              ["UserBenchmark", "www.userbenchmark.com", "#609070", /内蔵SSD|内蔵ハードディスク|PCパーツ>CPU|PCパーツ>グラフィックボード|USBメモリ/],
              ["Backblaze", "www.backblaze.com/blog/hard-drive-stats", "#C51E33", /内蔵SSD|内蔵ハードディスク/],
              ["kopfhoerer.com", "www.kopfhoerer.com", "#137db0", /用ヘッドセット|型ヘッドホン|Bluetooth対応ヘッドホン|ゲーミングヘッドセット|Bluetoothヘッドセット|イヤホンマイク>3.5mmミニプラグ|インナーイヤーヘッドホン|ヘッドセット・ヘッドホン|ヘッドホン>完全ワイヤレスイヤホン/],
              ["Kopfhoerer.de", "www.kopfhoerer.de", "#2b2a3a", /型ヘッドホン|Bluetooth対応ヘッドホン|インナーイヤーヘッドホン|ヘッドセット・ヘッドホン|ヘッドホン>完全ワイヤレスイヤホン/],
              ["RTINGS", "www.rtings.com", "#609070", /用ヘッドセット|型ヘッドホン|Bluetooth対応ヘッドホン|ゲーミングヘッドセット|Bluetoothヘッドセット|イヤホンマイク>3.5mmミニプラグ|インナーイヤーヘッドホン|ヘッドセット・ヘッドホン|ヘッドホン>完全ワイヤレスイヤホン/],
              ["TFT CENTRAL", "www.tftcentral.co.uk", "#2e2e2e", />ディスプレイ・モニター|ゲーミングモニター/]
            ]) {
            if ((issearch || isproduct || iscate) && ((titleEle.parentNode.parentNode.parentNode.innerText)).match(site[3])) { // RTINGS/kopfhoererリンク
              var ele = titleEle.parentNode.parentNode.parentNode.insertBefore(document.createElement("a"), titleEle.parentNode.parentNode.nextSibling);
              var iflsite = (Math.random() > ddg_google_ratio) ? "https://duckduckgo.com/?q=!ducky+" : "https://www.google.com/webhp?#btnI=I&q=";
              ele.innerHTML += '<a rel=\"noopener noreferrer nofollow\" href="' + iflsite + (titleEle.innerText.replace(/[\/\?\+\[\]\(\)\u30a0-\u30ff\u3040-\u309f\u3005-\u3006\u30e0-\u9fcf]+/gmi, " ") + ' ').replace(/\s{2,9}/gm, " ") + 'site:' + site[1] + '" 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:' + site[2] + '; color:white; white-space: nowrap; ">' + site[0] + '</a>';
            }
          }
        },
        pointFunc: (parentEle, price, priceEle, rndcolor) => {
          var pointEle = eleget0(SITE.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; }
          return point;
        },
        hide: (title, isorder, isproduct, issearch, parentEle, price, iscate, cppLimit, point, titleEle) => {
          // 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(/\s([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|m)/))) ? Number(title.match(/×([0-9\.\,]+)/)[1]) : 1;

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

              titleEle.style.display = "inline";
              var ele = $('<span class="ppr" data-ppr="' + pprEmbed + '" 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]) + (ppr == undefined ? ryou + "," + ryout + "," + mul : "") + '</span>').insertAfter(titleEle);
              if ((!isproduct) && (!isorder)) setClick(ele, ppr, "\\" + (Math.round(price - point)) + "/" + Number(ryout * mul) + (ryou[2] || ryou[4]) + "=" + pprEmbed + "\n") //$(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;

              titleEle.style.display = "inline";
              var ele = $('<span class="ppr2" 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>').insertAfter(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);
          if ((cppLimit[2] > 0 && pass2 == 0) || (cppLimit[2] > 0 && (!ryou))) debugRemove(parentEle);
        },
      }
    ];

    // -----------------------------------------------------

    var siteinfo = SITEINFO;
    //  var siteinfo = Object.assign(SITEINFO, pref("MY_SITEINFO") || []); //alert(siteinfo);return;

    // thissiteを決定
    var thissite = null;
    for (var i = 0; i < siteinfo.length; i++) {
      if (siteinfo[i].urlRE == "") break;
      if (location.href.match(siteinfo[i].urlRE)) {
        thissite = i;
        var SITE = Object.create(siteinfo[thissite]);
        break;
      }
    }
    if (thissite === null) return;

    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 cppLimit = [0, 0];

    if (SITE.onLoad) SITE.onLoad();
    var isorder = location.href.match(SITE.isorder);
    var issearch = location.href.match(SITE.issearch);
    var iscate = location.href.match(SITE.iscate);
    var isproduct = location.href.match(SITE.isproduct);
    var parentLimit = isorder ? SITE.orderPL : SITE.listPL;

    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 量あたり価格上限
        if (pressed == "a") sortByPpr(); // a::量単価小さい順で並べ替え
      }, false);
    }

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

    function sortByPpr() {
      if (SITE.sortData && location.href.match(SITE.sortUrl)) SITE.sortData.forEach(e => { sortEle(e.sortPpr, e.sortElement) });
      return;

      function sortEle(xpath, gen = "") {
        let p = elegeta(xpath).filter(e => e.offsetHeight > 0);
        let pele = elegeta(xpath + gen).filter(e => e.offsetHeight > 0); //console.log(p,pele)
        if (!p || !pele) return;
        pele.forEach((e, i) => e.pr = Number(p[i].dataset.ppr ? p[i].dataset.ppr : p[i].innerText.replace(/[^0-9.]/gmi, ""))); //console.log(pele[0].pr)
        let pele2 = pele.slice();
        //pele2 = pele2.sort((a, b) => { return a.pr > b.pr ? 1 : -1 });
        pele2 = pele2.sort((a, b) => { return Number(a.pr) > Number(b.pr) ? 1 : -1 });
        pele.forEach((e, i) => { e.outerHTML = pele2[i].outerHTML })
      }
    }

    function run(node) {
      if (SITE.onRun) { SITE.onRun(node, cppLimit); return; }
      for (let titleEle of elegeta(SITE.titleXPath, node)) {
        var rndcolor = '#' + (0x1000000 + (Math.random()) * 0xffffff).toString(16).substr(1, 6);
        var title = (titleEle.title ? titleEle.title : titleEle.innerText);
        if (titleEle.dataset.yCpP) { continue; } else { titleEle.dataset.yCpP = "1"; }
        debugEle(titleEle, rndcolor);
        var parentEle = titleEle;
        if (SITE.onEachItem) SITE.onEachItem(node, titleEle, rndcolor);
        for (var i = 0; i < parentLimit; i++) {
          parentEle = parentEle.parentNode;
          let found = elegeta(SITE.priceXPath, parentEle).length;
          if (found == 1) break;
          if (found > 1) i = parentLimit + 1;
        }
        if (i > parentLimit) continue;
        debugEle(parentEle, rndcolor);
        if (i == parentLimit) continue;
        var priceEle = eleget0(SITE.priceXPath, parentEle);
        if (!priceEle) continue;
        debugEle(priceEle, rndcolor);
        var price = Number(priceEle.innerText.match(/\D([0-9\,]+)/)[1].replace(/\,/g, ""));
        if (SITE.pointFunc) var point = SITE.pointFunc(parentEle, price, priceEle, rndcolor);
        if (SITE.hide) SITE.hide(title, isorder, isproduct, issearch, parentEle, price, iscate, cppLimit, point, titleEle);
      }
    }

    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, additionalInfo) {
      if (debug) {
        ele.style.outline = "3px dotted " + col;
        ele.style.boxShadow = " 0px 0px 4px 4px " + col + "30, inset 0 0 100px " + col + "20";
        //ele.outerHTML+=additionalInfo;
      }
    }

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

    function num(str) {
      return str.replace(/[A-Za-z0-9]/g, function(s) { return String.fromCharCode(s.charCodeAt(0) - 65248); });
    }

    function setClick(ele, ppr, shiki = "") {
      $(ele).css("cursor", "pointer").attr("title", shiki + "クリックかShift+Aでこの量単価の上限で絞り込む\n右クリックかAキーで量単価で並べ替え").click(e => inputcpplimit(e, 1, ppr)).on("contextmenu", e => {
        sortByPpr();
        e.preventDefault();
        return false;
      });
    }

  }
})();