您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
アニメイトオンラインショップのカートと「あとで買う」商品のリンクをMarkdown形式でクリップボードへコピーします。
// ==UserScript== // @name アニメイト通販:カートと「あとで買う」をMarkdownでコピー // @namespace hollen9.com // @version 1.0.0 // @description アニメイトオンラインショップのカートと「あとで買う」商品のリンクをMarkdown形式でクリップボードへコピーします。 // @match https://www.animate-onlineshop.jp/cart/* // @match https://www.animate-onlineshop.jp/cart/index.php* // @grant GM_setClipboard // @license MIT // ==/UserScript== (function () { 'use strict'; // Build absolute URL from <a> element const absUrl = (a) => new URL(a.getAttribute('href'), location.origin).toString(); // Sanitize text: remove zero-width chars, collapse spaces, trim const clean = (s) => (s || '') .replace(/[\u200B-\u200D\uFEFF]/g, '') .replace(/\s+/g, ' ') .trim(); // Get item title from a <tr> with multiple fallbacks function getTitleFromRow(tr) { // Primary: <h3><a> let a = tr.querySelector('.cart_item_info h3 a'); // Fallback: any product link in detail area if (!a) a = tr.querySelector('.cart_item_detail a[href^="/pd/"]'); if (!a) return ''; // Prefer visible text let t = clean(a.textContent || a.innerText || ''); // Fallback to title attribute if (!t) t = clean(a.getAttribute('title')); // Fallback to image alt in the same row if (!t) { const img = tr.querySelector('.cart_item_detail img[alt]'); if (img) t = clean(img.getAttribute('alt')); } return t; } // Extract items (title, url, price, qty, release) from a cart section function extractItemsFromSection(sectionEl) { const rows = sectionEl.querySelectorAll('tbody > tr'); const items = []; rows.forEach((tr) => { const link = tr.querySelector('.cart_item_info h3 a, .cart_item_detail a[href^="/pd/"]'); if (!link) return; const title = getTitleFromRow(tr) || '(no title)'; const url = absUrl(link); const priceEl = tr.querySelector('.cart_item_price'); const price = clean(priceEl ? priceEl.textContent : ''); // Quantity: cart uses <span class="num">1</span>; buy-later shows "数量:1" let qty = 1; const qtyEl = tr.querySelector('.fl_cart_item_num .num'); if (qtyEl) { const m = clean(qtyEl.textContent).match(/(\d+)/); if (m) qty = parseInt(m[1], 10); } // Release text (optional) let release = ''; const releases = tr.querySelectorAll('.cart_item_release'); releases.forEach((p) => { const t = clean(p.textContent); if (t.includes('発売日')) release = t; }); items.push({ title, url, price, qty, release }); }); return items; } // Build Markdown output function buildMarkdown(cartItems, laterItems) { const lines = []; if (cartItems.length) { lines.push('## カート'); cartItems.forEach((it) => { lines.push(`- [${it.title}](${it.url}) ×${it.qty} — ${it.price}${it.release ? ` — ${it.release}` : ''}`); }); lines.push(''); } if (laterItems.length) { lines.push('## あとで買う'); laterItems.forEach((it) => { lines.push(`- [${it.title}](${it.url}) ×${it.qty} — ${it.price}${it.release ? ` — ${it.release}` : ''}`); }); lines.push(''); } lines.push(`_from: ${location.href}_`); return lines.join('\n'); } // Copy text to clipboard (GM_setClipboard → navigator.clipboard → fallback) function copyToClipboard(text) { try { if (typeof GM_setClipboard === 'function') { GM_setClipboard(text, { type: 'text', mimetype: 'text/plain' }); return Promise.resolve(); } } catch {} if (navigator.clipboard?.writeText) return navigator.clipboard.writeText(text); const ta = document.createElement('textarea'); ta.value = text; document.body.appendChild(ta); ta.select(); document.execCommand('copy'); ta.remove(); return Promise.resolve(); } // Simple toast UI function toast(msg) { const t = document.createElement('div'); t.textContent = msg; Object.assign(t.style, { position: 'fixed', right: '16px', bottom: '72px', background: 'rgba(0,0,0,0.88)', color: '#fff', padding: '8px 12px', borderRadius: '10px', fontSize: '12px', zIndex: 999999, maxWidth: '60vw', lineHeight: 1.5, }); document.body.appendChild(t); setTimeout(() => t.remove(), 2600); } // Create floating action button (Japanese UI) function makeButton() { const btn = document.createElement('button'); btn.textContent = 'カートをMarkdownでコピー'; Object.assign(btn.style, { position: 'fixed', right: '16px', bottom: '16px', padding: '10px 14px', background: '#00a0e9', color: '#fff', border: 'none', borderRadius: '12px', fontWeight: '600', cursor: 'pointer', boxShadow: '0 6px 18px rgba(0,0,0,0.2)', zIndex: 999999, }); btn.addEventListener('click', async () => { try { // Find sections: first cart section = main cart, section with h2 "あとで買う" const sections = Array.from(document.querySelectorAll('section.cart')); let cartSection = null; let laterSection = null; sections.forEach((sec) => { const h2 = sec.querySelector('h2'); if (h2 && h2.textContent.includes('あとで買う')) { laterSection = sec; } else if (!cartSection) { cartSection = sec; } }); const cartItems = cartSection ? extractItemsFromSection(cartSection) : []; const laterItems = laterSection ? extractItemsFromSection(laterSection) : []; if (!cartItems.length && !laterItems.length) { toast('商品が見つかりません。カートページにいるかご確認ください。'); return; } const md = buildMarkdown(cartItems, laterItems); await copyToClipboard(md); toast(`Markdownとしてコピーしました(合計 ${cartItems.length + laterItems.length} 件)。\nコンソールにも出力しました。`); console.log('=== アニメイト カート Markdown ===\n' + md); } catch (err) { console.error(err); toast('コピーに失敗しました。開発者ツール(F12)のコンソールをご確認ください。'); } }); document.body.appendChild(btn); } // Init if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', makeButton); } else { makeButton(); } })();