Alt+PでAmazon・楽天・Yahoo!・ヨドバシ・ビックカメラのリンクと合計金額=本体価格+送料を計算し、標準プロンプト窓に表示|Tampermonkey

// ==UserScript==
// @name         Total Price Format to Prompt (Alt+P)
// @namespace    http://tampermonkey.net/
// @version      10.1
// @description  Alt+PでAmazon・楽天・Yahoo!・ヨドバシ・ビックカメラの合計金額とリンクを計算し、標準プロンプト窓に表示
// @match        https://www.amazon.co.jp/*
// @match        https://*.rakuten.co.jp/*
// @match        https://*.yahoo.co.jp/*
// @match        https://www.yodobashi.com/*
// @match        https://www.biccamera.com/*
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    document.addEventListener('keydown', function(event) {
        // Alt + P の判定
        if (event.altKey && !event.ctrlKey && !event.shiftKey && (event.key === 'p' || event.key === 'P' || event.code === 'KeyP')) {
            event.preventDefault();

            var d = document, u = location.href, t = d.title;
            var priceNum = 0;
            var shippingNum = 0;
            var displayPriceStr = '価格不明';

            function parseNum(str) {
                return str ? Number(String(str).replace(/[0-9]/g, c => String.fromCharCode(c.charCodeAt(0) - 0xFEE0)).replace(/[^\d]/g, '')) || 0 : 0;
            }

            // ==========================================
            // 1. Amazon の処理
            // ==========================================
            if (u.includes('amazon.co.jp')) {
                var eA = d.querySelector('#corePrice_feature_div .a-price .a-offscreen') ||
                       d.querySelector('#priceblock_ourprice') ||
                       d.querySelector('#priceblock_dealprice') ||
                       d.querySelector('.a-price .a-offscreen');
                if (eA) priceNum = parseNum(eA.textContent);

                var deliveryEl = d.querySelector('[data-csa-c-delivery-price]');
                if (deliveryEl) {
                    shippingNum = parseNum(deliveryEl.getAttribute('data-csa-c-delivery-price'));
                } else {
                    var shipElA = d.querySelector('#ourprice_shippingmessage') || d.querySelector('#delivery-message') || d.querySelector('[id*="shipping"]');
                    var shipTextA = shipElA ? shipElA.textContent : d.body.innerText;
                    if (shipTextA.includes('無料配送') || shipTextA.includes('Prime') || shipTextA.includes('プライム')) {
                        shippingNum = 0;
                    } else {
                        var mA = shipTextA.match(/配送料[^0-90-9]*([0-90-9,]+)円/);
                        if (mA) {
                            shippingNum = parseNum(mA[1]);
                        } else {
                            var mA2 = shipTextA.match(/¥\s*([0-90-9,]+)/);
                            if (mA2) shippingNum = parseNum(mA2[1]);
                        }
                    }
                }
                t = t.replace(/^Amazon\s*\|\s*/, '');
            }
            // ==========================================
            // 2. 楽天市場 の処理
            // ==========================================
            else if (u.includes('rakuten.co.jp')) {
                let jsonParsed = false;
                try {
                    const appDataEl = d.getElementById('item-page-app-data');
                    if (appDataEl) {
                        const jsonData = JSON.parse(appDataEl.textContent);
                        const skuInfo = jsonData.api?.itemInfoSku || jsonData.newApi?.itemInfoSku;
                        if (skuInfo) {
                            if (skuInfo.taxIncludedPrice) {
                                priceNum = Number(skuInfo.taxIncludedPrice);
                                jsonParsed = true;
                            }
                            if (skuInfo.shipping && skuInfo.shipping.postageIncluded) {
                                shippingNum = 0;
                            }
                        }
                    }
                } catch(err) {}

                if (!jsonParsed || priceNum === 0) {
                    var priceElR = d.querySelector('meta[property="product:price:amount"]') || d.querySelector('meta[itemprop="price"]');
                    if (priceElR) priceNum = parseNum(priceElR.getAttribute('content'));
                }

                if (shippingNum === 0) {
                    var shipElR = d.querySelector('[class*="postage"]') || d.querySelector('[class*="shipping"]') || d.querySelector('.price_delivery');
                    var shipTextR = shipElR ? shipElR.textContent : '';
                    if (shipTextR.includes('送料無料') || shipTextR.includes('送料込み')) {
                        shippingNum = 0;
                    } else {
                        var matchR = shipTextR.match(/([0-90-9,]+)\s*円/);
                        if (matchR) {
                            shippingNum = parseNum(matchR[1]);
                        } else {
                            var bodyMatchR = d.body.innerText.match(/送料\s*[::]?\s*([0-90-9,]+)円/);
                            if (bodyMatchR) shippingNum = parseNum(bodyMatchR[1]);
                        }
                    }
                }
            }
            // ==========================================
            // 3. Yahoo!ショッピング の処理
            // ==========================================
            else if (u.includes('yahoo.co.jp')) {
                t = t.replace(/\s*[:\-]\s*(Yahoo!ショッピング|通販).*$/i, '');

                var mp = d.querySelector('meta[property="product:price:amount"]') || d.querySelector('meta[itemprop="price"]');
                if (mp) priceNum = parseNum(mp.getAttribute('content') || mp.textContent);

                for (var sc of d.getElementsByTagName('script')) {
                    var tx = sc.textContent;
                    if (tx.includes('postageData') || tx.includes('taxIncludedPrice')) {
                        if (!priceNum) {
                            let m = tx.match(/"taxIncludedPrice"\s*:\s*(\d+)/);
                            if (m) priceNum = Number(m[1]);
                        }
                        if (tx.includes('"isPostageFree":true')) {
                            shippingNum = 0;
                        } else {
                            let m = tx.match(/"fee"\s*:\s*(\d+)/);
                            if (m) shippingNum = Number(m[1]);
                        }
                    }
                }

                if (shippingNum === 0 && d.body.innerText.includes('送料無料')) {
                    shippingNum = 0;
                } else if (shippingNum === 0) {
                    let m = d.body.innerText.match(/送料[^0-90-9]*([0-90-9,]+)円/);
                    if (m) shippingNum = parseNum(m[1]);
                }
            }
            // ==========================================
            // 4. ヨドバシカメラ の処理
            // ==========================================
            else if (u.includes('yodobashi.com')) {
                t = t.normalize('NFC').replace(/ヨドバシ\.com - (.*?) 通販【全品無料配達】/g, '$1');

                var priceElY = d.querySelector('.js_ppSalesPrice') || d.querySelector('meta[property="product:price:amount"]') || d.querySelector('meta[itemprop="price"]');
                if (priceElY) {
                    priceNum = parseNum(priceElY.getAttribute('content') || priceElY.textContent);
                }

                if (d.body.innerText.includes('全品配達料金無料') || d.body.innerText.includes('送料無料')) {
                    shippingNum = 0;
                } else {
                    let m = d.body.innerText.match(/送料[^0-90-9]*([0-90-9,]+)円/);
                    if (m) shippingNum = parseNum(m[1]);
                }
            }
            // ==========================================
            // 5. ビックカメラ の処理(強化版)
            // ==========================================
            else if (u.includes('biccamera.com')) {
                // タイトル整形(ビックカメラ.com - などを削除)
                t = t.normalize('NFC').replace(/^ビックカメラ\.com\s*-\s*/, '');

                // 価格の取得 ([itemprop="price"] またはメタタグ)
                var priceElB = d.querySelector('[itemprop="price"]') || d.querySelector('meta[property="product:price:amount"]');
                if (priceElB) {
                    priceNum = parseNum(priceElB.getAttribute('content') || priceElB.textContent);
                }

                // 送料の取得(DOM要素またはページ全体から「送料〇〇円」を柔軟に検索)
                var bodyText = d.body.innerText;

                // 「送料無料」が明示されている場合
                if (bodyText.includes('送料無料') && !bodyText.match(/送料\s*[1-90-9]/)) {
                    shippingNum = 0;
                } else {
                    // 「送料550円」などのパターンを正規表現で厳密に抽出(改行やスペースを許容)
                    var matchShipping = bodyText.match(/送料\s*([0-90-9,]+)\s*円/);
                    if (matchShipping) {
                        shippingNum = parseNum(matchShipping[1]);
                    } else {
                        shippingNum = 0; // 見つからない場合は0円(または基本無料)として扱う
                    }
                }
            }

            // 合計金額の組み立て
            if (priceNum > 0) {
                var totalNum = priceNum + shippingNum;
                displayPriceStr = totalNum.toLocaleString() + '円=' + priceNum.toLocaleString() + '+' + shippingNum.toLocaleString();
            }

            var l = '■<a href="' + u + '" target="_blank" rel="nofollow">【' + displayPriceStr + '】' + t + '</a><br>';

            // 標準プロンプト窓で表示(Enterキーで閉じられます)
            prompt('HTML', l);

            // クリップボードへのコピーも並行して試みる
            navigator.clipboard.writeText(l).catch(function(err) {});
        }
    });
})();

Alt+PでAmazon・楽天・Yahoo!・ヨドバシのリンクと合計金額=本体価格+送料を計算し、標準プロンプト窓に表示|Tampermonkey

// ==UserScript==
// @name         Total Price Format to Prompt (Alt+P)
// @namespace    http://tampermonkey.net/
// @version      9.0
// @description  Alt+PでAmazon・楽天・Yahoo!・ヨドバシの合計金額とリンクを計算し、標準プロンプト窓に表示
// @match        https://www.amazon.co.jp/*
// @match        https://*.rakuten.co.jp/*
// @match        https://*.yahoo.co.jp/*
// @match        https://www.yodobashi.com/*
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    document.addEventListener('keydown', function(event) {
        // Alt + P の判定
        if (event.altKey && !event.ctrlKey && !event.shiftKey && (event.key === 'p' || event.key === 'P' || event.code === 'KeyP')) {
            event.preventDefault();

            var d = document, u = location.href, t = d.title;
            var priceNum = 0;
            var shippingNum = 0;
            var displayPriceStr = '価格不明';

            function parseNum(str) {
                return str ? Number(String(str).replace(/[0-9]/g, c => String.fromCharCode(c.charCodeAt(0) - 0xFEE0)).replace(/[^\d]/g, '')) || 0 : 0;
            }

            // ==========================================
            // 1. Amazon の処理
            // ==========================================
            if (u.includes('amazon.co.jp')) {
                var eA = d.querySelector('#corePrice_feature_div .a-price .a-offscreen') ||
                       d.querySelector('#priceblock_ourprice') ||
                       d.querySelector('#priceblock_dealprice') ||
                       d.querySelector('.a-price .a-offscreen');
                if (eA) priceNum = parseNum(eA.textContent);

                var deliveryEl = d.querySelector('[data-csa-c-delivery-price]');
                if (deliveryEl) {
                    shippingNum = parseNum(deliveryEl.getAttribute('data-csa-c-delivery-price'));
                } else {
                    var shipElA = d.querySelector('#ourprice_shippingmessage') || d.querySelector('#delivery-message') || d.querySelector('[id*="shipping"]');
                    var shipTextA = shipElA ? shipElA.textContent : d.body.innerText;
                    if (shipTextA.includes('無料配送') || shipTextA.includes('Prime') || shipTextA.includes('プライム')) {
                        shippingNum = 0;
                    } else {
                        var mA = shipTextA.match(/配送料[^0-90-9]*([0-90-9,]+)円/);
                        if (mA) {
                            shippingNum = parseNum(mA[1]);
                        } else {
                            var mA2 = shipTextA.match(/¥\s*([0-90-9,]+)/);
                            if (mA2) shippingNum = parseNum(mA2[1]);
                        }
                    }
                }
                t = t.replace(/^Amazon\s*\|\s*/, '');
            }
            // ==========================================
            // 2. 楽天市場 の処理
            // ==========================================
            else if (u.includes('rakuten.co.jp')) {
                let jsonParsed = false;
                try {
                    const appDataEl = d.getElementById('item-page-app-data');
                    if (appDataEl) {
                        const jsonData = JSON.parse(appDataEl.textContent);
                        const skuInfo = jsonData.api?.itemInfoSku || jsonData.newApi?.itemInfoSku;
                        if (skuInfo) {
                            if (skuInfo.taxIncludedPrice) {
                                priceNum = Number(skuInfo.taxIncludedPrice);
                                jsonParsed = true;
                            }
                            if (skuInfo.shipping && skuInfo.shipping.postageIncluded) {
                                shippingNum = 0;
                            }
                        }
                    }
                } catch(err) {}

                if (!jsonParsed || priceNum === 0) {
                    var priceElR = d.querySelector('meta[property="product:price:amount"]') || d.querySelector('meta[itemprop="price"]');
                    if (priceElR) priceNum = parseNum(priceElR.getAttribute('content'));
                }

                if (shippingNum === 0) {
                    var shipElR = d.querySelector('[class*="postage"]') || d.querySelector('[class*="shipping"]') || d.querySelector('.price_delivery');
                    var shipTextR = shipElR ? shipElR.textContent : '';
                    if (shipTextR.includes('送料無料') || shipTextR.includes('送料込み')) {
                        shippingNum = 0;
                    } else {
                        var matchR = shipTextR.match(/([0-90-9,]+)\s*円/);
                        if (matchR) {
                            shippingNum = parseNum(matchR[1]);
                        } else {
                            var bodyMatchR = d.body.innerText.match(/送料\s*[::]?\s*([0-90-9,]+)円/);
                            if (bodyMatchR) shippingNum = parseNum(bodyMatchR[1]);
                        }
                    }
                }
            }
            // ==========================================
            // 3. Yahoo!ショッピング の処理
            // ==========================================
            else if (u.includes('yahoo.co.jp')) {
                t = t.replace(/\s*[:\-]\s*(Yahoo!ショッピング|通販).*$/i, '');

                var mp = d.querySelector('meta[property="product:price:amount"]') || d.querySelector('meta[itemprop="price"]');
                if (mp) priceNum = parseNum(mp.getAttribute('content') || mp.textContent);

                for (var sc of d.getElementsByTagName('script')) {
                    var tx = sc.textContent;
                    if (tx.includes('postageData') || tx.includes('taxIncludedPrice')) {
                        if (!priceNum) {
                            let m = tx.match(/"taxIncludedPrice"\s*:\s*(\d+)/);
                            if (m) priceNum = Number(m[1]);
                        }
                        if (tx.includes('"isPostageFree":true')) {
                            shippingNum = 0;
                        } else {
                            let m = tx.match(/"fee"\s*:\s*(\d+)/);
                            if (m) shippingNum = Number(m[1]);
                        }
                    }
                }

                if (shippingNum === 0 && d.body.innerText.includes('送料無料')) {
                    shippingNum = 0;
                } else if (shippingNum === 0) {
                    let m = d.body.innerText.match(/送料[^0-90-9]*([0-90-9,]+)円/);
                    if (m) shippingNum = parseNum(m[1]);
                }
            }
            // ==========================================
            // 4. ヨドバシカメラ の処理
            // ==========================================
            else if (u.includes('yodobashi.com')) {
                // ヨドバシ特有のタイトル整形(通販表記などを削除)
                t = t.normalize('NFC').replace(/ヨドバシ\.com - (.*?) 通販【全品無料配達】/g, '$1');

                var priceElY = d.querySelector('.js_ppSalesPrice') || d.querySelector('meta[property="product:price:amount"]') || d.querySelector('meta[itemprop="price"]');
                if (priceElY) {
                    priceNum = parseNum(priceElY.getAttribute('content') || priceElY.textContent);
                }

                // ヨドバシは全品基本送料無料(またはページ内で判定)
                if (d.body.innerText.includes('全品配達料金無料') || d.body.innerText.includes('送料無料')) {
                    shippingNum = 0;
                } else {
                    let m = d.body.innerText.match(/送料[^0-90-9]*([0-90-9,]+)円/);
                    if (m) shippingNum = parseNum(m[1]);
                }
            }

            // 合計金額の組み立て
            if (priceNum > 0) {
                var totalNum = priceNum + shippingNum;
                displayPriceStr = totalNum.toLocaleString() + '円=' + priceNum.toLocaleString() + '+' + shippingNum.toLocaleString();
            }

            var l = '■<a href="' + u + '" target="_blank" rel="nofollow">【' + displayPriceStr + '】' + t + '</a><br>';

            // 標準プロンプト窓で表示(Enterキーで閉じられます)
            prompt('HTML', l);

            // クリップボードへのコピーも並行して試みる
            navigator.clipboard.writeText(l).catch(function(err) {});
        }
    });
})();

Total Price Format to Prompt (Alt+P)をAutoHotkey 2.0で動かす

  • WindowsでAutoHotkeyを使用する場合は、‘AutoHotkey.ahk’へのショートカットをC:\Users\%username%\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startupに登録しておくと、起動時にAutoHotkeyを起動して常駐させておくことができる。
  • AutoHotkeyは’AutoHotkey.ahk’というファイル名、かつ、‘UTF-8(BOM付き)’、かつ、改行コード’CR+LF’のテキストファイルとして作る(以下の文字列をそのファイルにコピペすればよい)。
  • 以下のコードでは、Windows用キーボードの[アプリケーションキー(メニューキー)]を二度押しするとBraveのTampermonkeyを介して、任意のウェブページのリンクコードがクリップボードに得られるようになる。
#Requires AutoHotkey v2.0
#UseHook	; UseHookをOnにした状態で使用

AppsKey::
{
    key := "AppsKey"
    ErrorLevel := !KeyWait(key, "T0.3")
    If (ErrorLevel) {    ; 長押しした場合:MIFES
        Run('C:\Program Files\MIFES11\MIW.exe')
        KeyWait(key)
        return
    }
    
    ErrorLevel := !KeyWait(key, "D, T0.2")
    If (!ErrorLevel) {    ; 2度押しした場合:【旧:短押しの処理】Ctrl+. や GG 送信など
        Send("!p")    ; Alt+P を送信 (確実に戻すためにシンプルに記述)
        Sleep(300)    ; 待て
        
        A_Clipboard := ""
        Send("^a")
        Sleep(50)
        Send("^c")
        if ClipWait(0.5) {
            Send("{Enter}")
        }
        
        KeyWait(key)
        return
    } else {    ; 短押しした場合:【旧:2度押しの処理】Alt+C 送信など
        Send("!c")    ; Alt+C を送信 (確実に戻すためにシンプルに記述)
        Sleep(300)    ; ポップアップの表示を待つ
        
        ; クリップボードを一旦空にしてからコピーを試みる
        A_Clipboard := ""
        Send("^a")    ; 全選択
        Sleep(50)
        Send("^c")    ; コピー
        
        ; クリップボードにデータが入ったか少し待つ(最大0.5秒)
        if ClipWait(0.5) {
            Send("{Enter}")
        }
        
        KeyWait(key)
        return
    }
}
◀
 

↑