Last active
May 31, 2026 14:27
-
-
Save 3110/78ee3c08cc929f07e1e10e99f919cdc4 to your computer and use it in GitHub Desktop.
Amazon.co.jpのURLを短くするTampermonkey用UserScript
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // ==UserScript== | |
| // @name Amazon URL Shortener JP (ISBN-10 first, else 13->10, else ASIN) | |
| // @namespace http://tampermonkey.net/ | |
| // @version 1.1 | |
| // @description Shorten Amazon.co.jp URLs to /dp/ISBN-10 (books) or /dp/ASIN (others). If only ISBN-13 exists and starts with 978, convert to ISBN-10. | |
| // @match https://www.amazon.co.jp/* | |
| // @grant none | |
| // ==/UserScript== | |
| (() => { | |
| /** utils **/ | |
| const onlyDigitsX = s => s.replace(/[^\dXx]/g, ''); | |
| const isASIN = s => /^[A-Z0-9]{10}$/.test(s); | |
| const isISBN10 = s => /^\d{9}[\dX]$/i.test(s); | |
| const isISBN13 = s => /^(97[89])\d{10}$/.test(s); | |
| // ISBN-13 (978xxxxxxxxxxx) -> ISBN-10(※979は変換不可) | |
| const isbn13to10 = (isbn13) => { | |
| const d = isbn13.replace(/\D/g, ''); | |
| if (!/^978\d{10}$/.test(d)) return null; // 978以外は不可 | |
| const core9 = d.slice(3, 12); // 12桁中、出版社+書名の9桁 | |
| // ISBN-10 チェックデジット計算 | |
| let sum = 0; | |
| for (let i = 0; i < 9; i++) { | |
| sum += (10 - i) * parseInt(core9[i], 10); | |
| } | |
| const mod = sum % 11; | |
| const check = (11 - mod) % 11; | |
| const cd = (check === 10) ? 'X' : String(check); | |
| return core9 + cd; | |
| }; | |
| // URL から ASIN を拾う | |
| const getASINFromUrl = (url) => ( | |
| url.match(/\/dp\/([A-Z0-9]{10})(?:[/?#]|$)/)?.[1] || | |
| url.match(/\/gp\/product\/([A-Z0-9]{10})(?:[/?#]|$)/)?.[1] || | |
| url.match(/\/exec\/obidos\/ASIN\/([A-Z0-9]{10})(?:[/?#]|$)/)?.[1] || | |
| url.match(/\/([A-Z0-9]{10})(?:[/?#]|$)/)?.[1] || | |
| null | |
| ); | |
| // DOM から ASIN(保険) | |
| const getASINFromDom = () => { | |
| const el = document.querySelector('[data-asin]'); | |
| const v = el?.getAttribute('data-asin'); | |
| return (v && isASIN(v)) ? v : null; | |
| }; | |
| // 詳細欄テキスト収集 | |
| const collectDetailText = () => { | |
| const selectors = [ | |
| '#detailBullets_feature_div', | |
| '#detailBulletsWrapper_feature_div', | |
| '#detailBulletsWrapper', | |
| '#productDetails_detailBullets_sections1', | |
| '#productDetails_techSpec_section_1', | |
| '#productDetails_techSpec_section_2', | |
| '#prodDetails', | |
| '#detailBullets', | |
| '#detailBulletsReverse', | |
| '#productDetails_db_sections', | |
| ]; | |
| let text = ''; | |
| for (const sel of selectors) { | |
| const n = document.querySelector(sel); | |
| if (n) text += '\n' + n.innerText; | |
| } | |
| if (!text.trim()) text = document.body?.innerText || ''; | |
| return text; | |
| }; | |
| const getISBN10FromDom = () => { | |
| const txt = collectDetailText(); | |
| // 明示の「ISBN-10:」優先(ハイフン/空白許容) | |
| const m = txt.match(/ISBN[\s\-]*10[^0-9X]*([0-9Xx\-\s]{10,15})/); | |
| if (m) { | |
| const cand = onlyDigitsX(m[1]).toUpperCase(); | |
| if (isISBN10(cand)) return cand; | |
| } | |
| // 補助: 10桁候補の周辺に ISBN 文言がある場合のみ | |
| const generic = txt.match(/\b[0-9]{9}[0-9Xx]\b/g); | |
| if (generic) { | |
| for (const s of generic) { | |
| const idx = txt.indexOf(s); | |
| const around = txt.slice(Math.max(0, idx - 24), idx + s.length + 24); | |
| if (/ISBN/i.test(around)) { | |
| const cand = s.toUpperCase(); | |
| if (isISBN10(cand)) return cand; | |
| } | |
| } | |
| } | |
| return null; | |
| }; | |
| const getISBN13FromDom = () => { | |
| const txt = collectDetailText(); | |
| // 「ISBN-13: 978-...」のような明示表記 | |
| const m = txt.match(/ISBN[\s\-]*13[^0-9]*([0-9\-\s]{13,20})/i); | |
| if (m) { | |
| const cand = m[1].replace(/\D/g, ''); | |
| if (isISBN13(cand)) return cand; | |
| } | |
| // 広めに 978/979 始まり13桁(ハイフン区切り対応) | |
| const all13 = txt.match(/(97[89][\s\-]*\d[\s\-]*\d[\s\-]*\d[\s\-]*\d[\s\-]*\d[\s\-]*\d[\s\-]*\d[\s\-]*\d[\s\-]*\d[\s\-]*\d[\s\-]*\d)/g); | |
| if (all13) { | |
| for (const raw of all13) { | |
| const cand = raw.replace(/\D/g, ''); | |
| if (isISBN13(cand)) return cand; | |
| } | |
| } | |
| return null; | |
| }; | |
| const toShortUrl = (code) => `https://www.amazon.co.jp/dp/${code}`; | |
| const run = () => { | |
| const url = location.href; | |
| // 優先: ISBN-10 -> (無ければ) ISBN-13→10 -> (無ければ) ASIN | |
| let code = null; | |
| const isbn10 = getISBN10FromDom(); | |
| if (isbn10) { | |
| code = isbn10; | |
| } else { | |
| const isbn13 = getISBN13FromDom(); | |
| const isbn10from13 = isbn13 ? isbn13to10(isbn13) : null; | |
| if (isbn10from13) { | |
| code = isbn10from13; | |
| } else { | |
| const asin = getASINFromUrl(url) || getASINFromDom(); | |
| if (asin) code = asin; | |
| } | |
| } | |
| if (!code) { | |
| console.log('[Amazon Shortener] code not found'); | |
| return; | |
| } | |
| const shortUrl = toShortUrl(code); | |
| if (url !== shortUrl) { | |
| history.replaceState(null, '', shortUrl); | |
| console.log('[Amazon Shortener] shortened ->', shortUrl); | |
| } | |
| }; | |
| // 初回(SPA対策の遅延) | |
| const trigger = () => setTimeout(run, 800); | |
| trigger(); | |
| // SPA/遅延ロード対応(デバウンス) | |
| let lastUrl = location.href; | |
| let timer = null; | |
| const onDomChange = () => { | |
| const changed = location.href !== lastUrl; | |
| if (changed) lastUrl = location.href; | |
| clearTimeout(timer); | |
| timer = setTimeout(run, changed ? 800 : 1200); | |
| }; | |
| new MutationObserver(onDomChange).observe(document, { subtree: true, childList: true }); | |
| })(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment