Last active
January 4, 2026 11:12
-
-
Save mtane0412/6c1436c403ebed380dcfc0aeead61466 to your computer and use it in GitHub Desktop.
AmazonのHTML埋め込みリンクを作成するTamperMonkey Script
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 Link Card Generator | |
| // @namespace https://gist.github.com/mtane0412/6c1436c403ebed380dcfc0aeead61466/edit | |
| // @version 1.2.0 | |
| // @description Amazon商品ページでリンクカードHTMLを生成してクリップボードにコピー(アソシエイトリンク対応・プレビュー機能付き) | |
| // @author mtane0412 | |
| // @match https://www.amazon.co.jp/*/dp/* | |
| // @match https://www.amazon.co.jp/dp/* | |
| // @match https://www.amazon.co.jp/*/gp/product/* | |
| // @match https://www.amazon.co.jp/gp/product/* | |
| // @grant GM_setClipboard | |
| // @grant GM_addStyle | |
| // @grant GM_getValue | |
| // @grant GM_setValue | |
| // @run-at document-idle | |
| // ==/UserScript== | |
| /* eslint-env browser, greasemonkey */ | |
| /* global GM_setClipboard, GM_addStyle, GM_getValue, GM_setValue, window, document, setTimeout, console, URL, URLSearchParams */ | |
| (function () { | |
| 'use strict'; | |
| /** | |
| * Amazonメタデータ型定義 | |
| * @typedef {Object} AmazonMetadata | |
| * @property {string} title - 商品タイトル | |
| * @property {string} image - 商品画像URL | |
| * @property {string} description - 商品説明 | |
| * @property {string} [price] - 価格(取得できない場合はundefined) | |
| * @property {string} url - 元のAmazon URL | |
| */ | |
| /** | |
| * DOM要素からAmazonメタデータを抽出する | |
| * @returns {AmazonMetadata | null} 取得したメタデータ、失敗時はnull | |
| */ | |
| function extractMetadata() { | |
| // URLを正規化(トラッキングパラメータ除去)してアソシエイトリンクに変換 | |
| const normalizedUrl = normalizeAmazonURL(window.location.href); | |
| const url = convertToAssociateLink(normalizedUrl); | |
| // タイトル取得(優先順位: meta[name="title"] > og:title > #productTitle) | |
| const title = | |
| document.querySelector('meta[name="title"]')?.getAttribute('content') || | |
| document.querySelector('meta[property="og:title"]')?.getAttribute('content') || | |
| document.querySelector('#productTitle')?.textContent?.trim(); | |
| if (!title) { | |
| console.error('[Amazon Link Card] タイトルを取得できませんでした'); | |
| return null; | |
| } | |
| // 画像URL取得(優先順位: #landingImage > og:image) | |
| const image = | |
| document.querySelector('#landingImage')?.getAttribute('src') || | |
| document.querySelector('meta[property="og:image"]')?.getAttribute('content'); | |
| if (!image) { | |
| console.error('[Amazon Link Card] 画像URLを取得できませんでした'); | |
| return null; | |
| } | |
| // 説明取得(優先順位: meta[name="description"] > og:description > feature-bullets) | |
| const description = | |
| document.querySelector('meta[name="description"]')?.getAttribute('content') || | |
| document.querySelector('meta[property="og:description"]')?.getAttribute('content') || | |
| document.querySelector('#feature-bullets')?.textContent?.trim().substring(0, 150); | |
| if (!description) { | |
| console.error('[Amazon Link Card] 説明を取得できませんでした'); | |
| return null; | |
| } | |
| // 価格取得(複数パターンに対応、優先順位付き) | |
| let price = undefined; | |
| // パターン1: デスクトップ版のメイン価格表示(最優先) | |
| // .a-offscreenはスクリーンリーダー用の隠し要素で、完全な価格文字列が含まれる | |
| const corePriceDisplay = document.querySelector( | |
| '#corePriceDisplay_desktop_feature_div .a-price[data-a-color="price"] .a-offscreen' | |
| ); | |
| if (corePriceDisplay) { | |
| price = corePriceDisplay.textContent?.trim(); | |
| } | |
| // パターン2: モバイル版のメイン価格表示 | |
| if (!price) { | |
| const mobileCorePrice = document.querySelector( | |
| '#corePrice_desktop .a-price .a-offscreen' | |
| ); | |
| if (mobileCorePrice) { | |
| price = mobileCorePrice.textContent?.trim(); | |
| } | |
| } | |
| // パターン3: apex_desktop配下の価格 | |
| if (!price) { | |
| const apexPrice = document.querySelector('#apex_desktop .a-price .a-offscreen'); | |
| if (apexPrice) { | |
| price = apexPrice.textContent?.trim(); | |
| } | |
| } | |
| // パターン4: priceblock系のID(従来型) | |
| if (!price) { | |
| const priceblock = | |
| document.querySelector('#priceblock_ourprice')?.textContent?.trim() || | |
| document.querySelector('#priceblock_dealprice')?.textContent?.trim() || | |
| document.querySelector('#price_inside_buybox')?.textContent?.trim(); | |
| if (priceblock) { | |
| price = priceblock; | |
| } | |
| } | |
| // パターン5: a-price-wholeとa-price-fractionの組み合わせ(最終手段) | |
| // corePriceDisplay配下に限定して誤検出を防ぐ | |
| if (!price) { | |
| const corePriceWhole = document.querySelector( | |
| '#corePriceDisplay_desktop_feature_div .a-price-whole' | |
| ); | |
| const corePriceFraction = document.querySelector( | |
| '#corePriceDisplay_desktop_feature_div .a-price-fraction' | |
| ); | |
| if (corePriceWhole) { | |
| const whole = corePriceWhole.textContent?.trim(); | |
| const fraction = corePriceFraction?.textContent?.trim(); | |
| price = fraction ? `¥${whole}${fraction}` : `¥${whole}`; | |
| } | |
| } | |
| return { title, image, description, price, url }; | |
| } | |
| /** | |
| * URLを正規化する(トラッキングパラメータ除去) | |
| * @param {string} url - 正規化対象のURL | |
| * @returns {string} 正規化されたURL | |
| */ | |
| function normalizeAmazonURL(url) { | |
| const parsed = new URL(url); | |
| const cleanParams = new URLSearchParams(); | |
| // トラッキングパラメータを除去 | |
| // ref, _, th で始まるパラメータは削除 | |
| for (const [key, value] of parsed.searchParams) { | |
| if (!key.startsWith('ref') && !key.startsWith('_') && key !== 'th') { | |
| cleanParams.set(key, value); | |
| } | |
| } | |
| parsed.search = cleanParams.toString(); | |
| return parsed.toString(); | |
| } | |
| /** | |
| * AmazonアソシエイトリンクURLを生成する | |
| * URLをASIN形式に変換し、アソシエイトIDタグを付与します。 | |
| * @param {string} url - 変換対象のAmazon URL | |
| * @returns {string} アソシエイトリンクURL | |
| */ | |
| function convertToAssociateLink(url) { | |
| const associateId = GM_getValue('amazonAssociateId', ''); | |
| // ASINを抽出(/dp/ASIN または /gp/product/ASIN 形式) | |
| const asinMatch = url.match(/\/(?:dp|gp\/product)\/([A-Z0-9]{10})/); | |
| if (!asinMatch) { | |
| // ASIN抽出失敗時は元のURLを返す | |
| return url; | |
| } | |
| const asin = asinMatch[1]; | |
| // アソシエイトIDが設定されている場合はアソシエイトリンクを生成 | |
| if (associateId) { | |
| return `https://www.amazon.co.jp/dp/${asin}/ref=nosim?tag=${encodeURIComponent(associateId)}`; | |
| } | |
| // アソシエイトIDが未設定の場合はシンプルなASIN URLを返す | |
| return `https://www.amazon.co.jp/dp/${asin}`; | |
| } | |
| /** | |
| * HTMLエスケープ処理 | |
| * XSS対策のため、HTMLの特殊文字をエスケープします。 | |
| * @param {string} str - エスケープ対象の文字列 | |
| * @returns {string} エスケープされた文字列 | |
| */ | |
| function escapeHTML(str) { | |
| return str | |
| .replace(/&/g, '&') | |
| .replace(/</g, '<') | |
| .replace(/>/g, '>') | |
| .replace(/"/g, '"') | |
| .replace(/'/g, '''); | |
| } | |
| /** | |
| * Ghost embed用のリンクカードHTMLを生成する | |
| * 完全に自己完結したインラインCSSを使用し、外部CSSに依存しません。 | |
| * @param {AmazonMetadata} metadata - Amazonメタデータ | |
| * @returns {string} 生成されたHTMLコード | |
| */ | |
| function generateLinkCardHTML(metadata) { | |
| return ` | |
| <div class="amazon-link-card" style=" | |
| max-width: 600px; | |
| border: 1px solid #ddd; | |
| border-radius: 8px; | |
| overflow: hidden; | |
| display: flex; | |
| font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; | |
| box-shadow: 0 2px 8px rgba(0,0,0,0.1); | |
| margin: 20px auto; | |
| "> | |
| <a href="${escapeHTML(metadata.url)}" | |
| target="_blank" | |
| rel="noopener noreferrer" | |
| style="text-decoration: none; color: inherit; display: flex; flex-direction: row;"> | |
| <div style="flex: 0 0 180px; background: #f7f7f7; display: flex; align-items: center; justify-content: center; padding: 16px;"> | |
| <img src="${escapeHTML(metadata.image)}" | |
| alt="${escapeHTML(metadata.title)}" | |
| style="max-width: 100%; max-height: 200px; object-fit: contain;"> | |
| </div> | |
| <div style="flex: 1; padding: 16px; display: flex; flex-direction: column; justify-content: space-between;"> | |
| <div> | |
| <div style="margin: 0 0 8px 0; font-size: 16px; font-weight: 600; line-height: 1.4; color: #111;"> | |
| ${escapeHTML(metadata.title)} | |
| </div> | |
| <p style="margin: 0 0 12px 0; font-size: 14px; color: #666; line-height: 1.5;"> | |
| ${escapeHTML(metadata.description)} | |
| </p> | |
| </div> | |
| ${ | |
| metadata.price | |
| ? ` | |
| <div style="display: flex; align-items: center; justify-content: space-between;"> | |
| <span style="font-size: 18px; font-weight: 700; color: #B12704;"> | |
| ${escapeHTML(metadata.price)} | |
| </span> | |
| <span style="font-size: 12px; color: #0066c0; font-weight: 500;"> | |
| Amazonで見る → | |
| </span> | |
| </div> | |
| ` | |
| : ` | |
| <div style="text-align: right;"> | |
| <span style="font-size: 12px; color: #0066c0; font-weight: 500;"> | |
| Amazonで見る → | |
| </span> | |
| </div> | |
| ` | |
| } | |
| </div> | |
| </a> | |
| </div> | |
| <style> | |
| @media (max-width: 600px) { | |
| .amazon-link-card a { | |
| flex-direction: column !important; | |
| } | |
| .amazon-link-card a > div:first-child { | |
| flex: 0 0 auto !important; | |
| padding: 20px !important; | |
| } | |
| } | |
| </style> | |
| `.trim(); | |
| } | |
| /** | |
| * トースト通知を表示する | |
| * @param {string} message - 表示するメッセージ | |
| * @param {'success' | 'error'} type - 通知タイプ | |
| */ | |
| function showToast(message, type = 'success') { | |
| const toast = document.createElement('div'); | |
| toast.className = `amazon-link-card-toast amazon-link-card-toast-${type}`; | |
| toast.textContent = message; | |
| document.body.appendChild(toast); | |
| // アニメーション用のタイムアウト | |
| setTimeout(() => toast.classList.add('show'), 10); | |
| // 3秒後に非表示 | |
| setTimeout(() => { | |
| toast.classList.remove('show'); | |
| setTimeout(() => toast.remove(), 300); | |
| }, 3000); | |
| } | |
| /** | |
| * リンクカードプレビューポップアップを表示する | |
| * メタデータの取得、プレビュー表示、アソシエイトID設定、コピー機能を統合したポップアップを表示します。 | |
| */ | |
| function showLinkCardPopup() { | |
| // メタデータを取得 | |
| const metadata = extractMetadata(); | |
| if (!metadata) { | |
| showToast('メタデータの取得に失敗しました', 'error'); | |
| return; | |
| } | |
| const html = generateLinkCardHTML(metadata); | |
| const currentId = GM_getValue('amazonAssociateId', ''); | |
| // ポップアップ背景 | |
| const overlay = document.createElement('div'); | |
| overlay.id = 'amazon-link-card-popup-overlay'; | |
| overlay.innerHTML = ` | |
| <div id="amazon-link-card-popup"> | |
| <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px;"> | |
| <h3 style="margin: 0; font-size: 18px; font-weight: 600;">リンクカード</h3> | |
| <button id="amazon-link-card-close-btn" style="background: none; border: none; font-size: 24px; cursor: pointer; color: #666; padding: 0; width: 32px; height: 32px; display: flex; align-items: center; justify-content: center;" title="閉じる"> | |
| × | |
| </button> | |
| </div> | |
| <!-- プレビューセクション --> | |
| <div style="margin-bottom: 20px;"> | |
| <h4 style="margin: 0 0 12px 0; font-size: 14px; font-weight: 600; color: #111;">プレビュー</h4> | |
| <div id="amazon-link-card-preview" style="border: 1px solid #ddd; border-radius: 4px; padding: 16px; background: #f9f9f9; max-height: 400px; overflow-y: auto;"> | |
| ${html} | |
| </div> | |
| </div> | |
| <!-- アソシエイトID設定セクション --> | |
| <div style="margin-bottom: 20px;"> | |
| <h4 style="margin: 0 0 8px 0; font-size: 14px; font-weight: 600; color: #111;">アソシエイトID設定</h4> | |
| <p style="margin: 0 0 8px 0; font-size: 12px; color: #666;"> | |
| Amazonアソシエイトプログラムのトラッキングタグを設定できます。 | |
| </p> | |
| <div style="display: flex; gap: 8px; align-items: center;"> | |
| <input | |
| type="text" | |
| id="amazon-associate-id-input" | |
| placeholder="例: yourname-22" | |
| value="${escapeHTML(currentId)}" | |
| style="flex: 1; padding: 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px; box-sizing: border-box;" | |
| /> | |
| <button id="amazon-associate-id-save-btn" style="padding: 10px 16px; border: none; background: #0066c0; color: white; border-radius: 4px; cursor: pointer; font-size: 14px; font-weight: 500; white-space: nowrap;"> | |
| 保存 | |
| </button> | |
| </div> | |
| ${ | |
| currentId | |
| ? `<p style="margin: 8px 0 0 0; font-size: 12px; color: #0066c0;">現在のID: <strong>${escapeHTML(currentId)}</strong></p>` | |
| : '' | |
| } | |
| </div> | |
| <!-- アクションボタン --> | |
| <div style="display: flex; gap: 8px; justify-content: flex-end;"> | |
| <button id="amazon-link-card-cancel-btn" style="padding: 10px 20px; border: 1px solid #ddd; background: white; border-radius: 4px; cursor: pointer; font-size: 14px;"> | |
| キャンセル | |
| </button> | |
| <button id="amazon-link-card-copy-popup-btn" style="padding: 10px 20px; border: none; background: #ff9900; color: #111; border-radius: 4px; cursor: pointer; font-weight: 600; font-size: 14px;"> | |
| HTMLをコピー | |
| </button> | |
| </div> | |
| </div> | |
| `; | |
| document.body.appendChild(overlay); | |
| // 閉じるボタン | |
| document.getElementById('amazon-link-card-close-btn')?.addEventListener('click', () => { | |
| overlay.remove(); | |
| }); | |
| // キャンセルボタン | |
| document.getElementById('amazon-link-card-cancel-btn')?.addEventListener('click', () => { | |
| overlay.remove(); | |
| }); | |
| // アソシエイトID保存ボタン | |
| document.getElementById('amazon-associate-id-save-btn')?.addEventListener('click', () => { | |
| const input = document.getElementById('amazon-associate-id-input'); | |
| const newId = input?.value.trim() || ''; | |
| GM_setValue('amazonAssociateId', newId); | |
| showToast( | |
| newId ? 'アソシエイトIDを保存しました' : 'アソシエイトIDをクリアしました', | |
| 'success' | |
| ); | |
| // プレビューを更新(アソシエイトIDを反映) | |
| setTimeout(() => { | |
| const updatedMetadata = extractMetadata(); | |
| if (updatedMetadata) { | |
| const updatedHtml = generateLinkCardHTML(updatedMetadata); | |
| const previewElement = document.getElementById('amazon-link-card-preview'); | |
| if (previewElement) { | |
| previewElement.innerHTML = updatedHtml; | |
| } | |
| } | |
| }, 100); | |
| }); | |
| // HTMLコピーボタン | |
| document.getElementById('amazon-link-card-copy-popup-btn')?.addEventListener('click', () => { | |
| try { | |
| GM_setClipboard(html); | |
| showToast('リンクカードをクリップボードにコピーしました!', 'success'); | |
| overlay.remove(); | |
| } catch (error) { | |
| console.error('[Amazon Link Card] コピーに失敗しました:', error); | |
| showToast('コピーに失敗しました', 'error'); | |
| } | |
| }); | |
| // 背景クリックで閉じる | |
| overlay.addEventListener('click', (e) => { | |
| if (e.target === overlay) { | |
| overlay.remove(); | |
| } | |
| }); | |
| } | |
| /** | |
| * リンクカードボタンを生成してページに追加する | |
| */ | |
| function addLinkCardButton() { | |
| // Amazonアソシエイトツールバーのリンク生成ボタンのコンテナを探す | |
| const getLinkContainer = document.querySelector('.amzn-ss-get-link-container'); | |
| if (getLinkContainer) { | |
| // ツールバー内にボタンを配置(リンク生成ボタンの隣に配置) | |
| const container = document.createElement('div'); | |
| container.className = 'amzn-ss-link-container amazon-link-card-toolbar-container'; | |
| container.innerHTML = ` | |
| <span class="a-declarative"> | |
| <button title="リンクカード" id="amazon-link-card-open-btn" class="a-button a-button-primary"> | |
| <span class="a-button-inner"> | |
| <span class="a-button-text">リンクカード</span> | |
| </span> | |
| </button> | |
| </span> | |
| `; | |
| // ツールバーの「リンク生成」ボタンの隣(後ろ)に挿入 | |
| getLinkContainer.insertAdjacentElement('afterend', container); | |
| } else { | |
| // ツールバーが見つからない場合は従来通り右下に配置 | |
| const container = document.createElement('div'); | |
| container.id = 'amazon-link-card-button-fallback'; | |
| container.innerHTML = ` | |
| <button id="amazon-link-card-open-btn"> | |
| <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> | |
| <rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect> | |
| <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path> | |
| </svg> | |
| <span>リンクカード</span> | |
| </button> | |
| `; | |
| document.body.appendChild(container); | |
| } | |
| // リンクカードボタンクリックイベント | |
| const button = document.getElementById('amazon-link-card-open-btn'); | |
| button?.addEventListener('click', showLinkCardPopup); | |
| } | |
| // スタイルを追加 | |
| GM_addStyle(` | |
| /* ツールバー統合用のスタイル */ | |
| .amazon-link-card-toolbar-container { | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| } | |
| .amazon-link-card-toolbar-container .amzn-ss-icon { | |
| background: transparent; | |
| border: none; | |
| cursor: pointer; | |
| padding: 8px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| transition: opacity 0.2s ease; | |
| } | |
| .amazon-link-card-toolbar-container .amzn-ss-icon:hover { | |
| opacity: 0.7; | |
| } | |
| /* フォールバック用:ボタンコンテナのスタイル */ | |
| #amazon-link-card-button-fallback { | |
| position: fixed; | |
| bottom: 20px; | |
| right: 20px; | |
| z-index: 10000; | |
| display: flex; | |
| gap: 8px; | |
| } | |
| /* フォールバック用:リンクカードボタンのスタイル */ | |
| #amazon-link-card-button-fallback #amazon-link-card-open-btn { | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| background: #ff9900; | |
| color: #111; | |
| border: none; | |
| border-radius: 8px; | |
| padding: 12px 20px; | |
| font-size: 14px; | |
| font-weight: 600; | |
| cursor: pointer; | |
| box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2); | |
| transition: all 0.2s ease; | |
| } | |
| #amazon-link-card-button-fallback #amazon-link-card-open-btn:hover { | |
| background: #ffad33; | |
| transform: translateY(-2px); | |
| box-shadow: 0 6px 16px rgba(0, 0, 0, 0.3); | |
| } | |
| #amazon-link-card-button-fallback #amazon-link-card-open-btn:active { | |
| transform: translateY(0); | |
| } | |
| /* ポップアップのスタイル */ | |
| #amazon-link-card-popup-overlay { | |
| position: fixed; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| background: rgba(0, 0, 0, 0.5); | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| z-index: 10002; | |
| } | |
| #amazon-link-card-popup { | |
| background: white; | |
| border-radius: 8px; | |
| padding: 24px; | |
| max-width: 700px; | |
| width: 90%; | |
| max-height: 90vh; | |
| overflow-y: auto; | |
| box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3); | |
| font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; | |
| } | |
| /* トースト通知のスタイル */ | |
| .amazon-link-card-toast { | |
| position: fixed; | |
| top: 20px; | |
| right: 20px; | |
| padding: 16px 24px; | |
| border-radius: 8px; | |
| font-size: 14px; | |
| font-weight: 500; | |
| box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2); | |
| z-index: 10001; | |
| opacity: 0; | |
| transform: translateY(-20px); | |
| transition: all 0.3s ease; | |
| } | |
| .amazon-link-card-toast.show { | |
| opacity: 1; | |
| transform: translateY(0); | |
| } | |
| .amazon-link-card-toast-success { | |
| background: #4caf50; | |
| color: white; | |
| } | |
| .amazon-link-card-toast-error { | |
| background: #f44336; | |
| color: white; | |
| } | |
| `); | |
| // ページ読み込み完了後にボタンを追加 | |
| if (document.readyState === 'loading') { | |
| document.addEventListener('DOMContentLoaded', addLinkCardButton); | |
| } else { | |
| addLinkCardButton(); | |
| } | |
| })(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment