// ==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 https://greasyfork.org/users/181558 // @run-at document-idle // @downloadURL none // ==/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 = $('¥' + ppr + '/' + (ryou[2] || ryou[4]) + (ppr == undefined ? ryou + "," + ryout + "," + mul : "") + '').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 = $('¥' + ppr2 + '/' + (ryou[2] || ryou[4]) + '').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 += " " + "(還元後:" + "¥" + pricef.toLocaleString() + ")"; } } 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 = $('¥' + ppr + '/' + (ryou[2] || ryou[4]) + '').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 = $('¥' + ppr + '/' + (ryou[2] || ryou[4]) + (ppr == undefined ? ryou + "," + ryout + "," + mul : "") + '').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 = $('¥' + ppr2 + '/' + (ryou[2] || ryou[4]) + '').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 += '' + site[0] + ''; } } }, 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 += "(" + Math.round(pointPer * 100) + "%?)"; } if (pointPer) { var point = Math.ceil(price - (price * (price / (price + price * pointPer)))); var pricef = Math.round(price - point).toLocaleString(); if (enableBeta) priceEle.innerHTML += " " + (isorder ? "
還元後:" : "(還元後:") + "¥" + pricef + (isorder ? "" : ")") + "
"; } } 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 = $('¥' + ppr + '/' + (ryou[2] || ryou[4]) + (ppr == undefined ? ryou + "," + ryout + "," + mul : "") + '').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 = $('¥' + ppr2 + '/' + (ryou[2] || ryou[4]) + '').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 $('' + str + '').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; }); } } })();