您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Takes a TCGPlayer Cart/History and spits out a CSV
// ==UserScript== // @name TCGPlayer Cart/History to CSV in Console // @namespace http://tampermonkey.net/ // @version 10-01-2025 // @description Takes a TCGPlayer Cart/History and spits out a CSV // @author multimeric, ganondorc, micool777 // @match https://cart.tcgplayer.com/* // @match https://store.tcgplayer.com/myaccount/orderhistory // @match https://store.tcgplayer.com/shoppingcart/review // @match https://www.tcgplayer.com/checkout // @match https://www.tcgplayer.com/cart // @icon https://www.google.com/s2/favicons?sz=64&domain=tcgplayer.com // @grant none // @require http://code.jquery.com/jquery-3.4.1.min.js // @license MIT // ==/UserScript== (function () { 'use strict'; /* -------------------- helpers -------------------- */ function createButtonRow(buttonText, onClickHandler, testid) { const row = document.createElement('div'); row.className = 'button-row'; row.style.paddingBottom = '5px'; row.style.paddingTop = '5px'; const button = document.createElement('button'); button.className = 'tcg-button tcg-button--md tcg-standard-button tcg-standard-button--priority is-full-width checkout-btn'; button.type = 'button'; button.dataset.testid = testid; const buttonContent = document.createElement('span'); buttonContent.className = 'tcg-standard-button__content'; const spanText = document.createElement('span'); spanText.textContent = buttonText; buttonContent.appendChild(spanText); button.appendChild(buttonContent); button.addEventListener('click', onClickHandler); row.appendChild(button); return row; } const $$ = (root, sel) => Array.from(root.querySelectorAll(sel)); /* -------------------- CSV: order history (unchanged) -------------------- */ function generateOrderHistoryCSV() { let csv = 'Name,Rarity,Condition,Individual Price,Quantity\n'; for (let order of $$(document, '.orderWrap')) { for (let tr of $$(order, '.orderTable tr')) { try { const name = '"' + $$(tr, '.nocontext')[0].innerText.trim() + '"'; const details = $$(tr, '.orderHistoryDetail')[0]; const rarity = details.childNodes[0].textContent.split(':')[1]; const condition = details.childNodes[2].textContent.split(':')[1]; const price = $$(tr, '.orderHistoryPrice')[0].innerText; const qty = $$(tr, '.orderHistoryQuantity')[0].innerText; csv += [name, rarity, condition, price, qty].map(s => (s || '').trim()).join(',') + '\n'; } catch { /* skip non-item rows */ } } } return csv; } /* -------------------- CSV: cart/checkout (NEW DOM first, fallback to old) -------------------- */ function generateCartCSV_NewCheckout() { // Target the structure in your snippet: ul.item-list > li.list-item > section.package-item ... const items = $$(document, 'ul.item-list li.list-item section.package-item section.content .description'); if (!items.length) return null; let csv = 'Name,Set,Rarity,Condition,Individual Price,Quantity\n'; for (const desc of items) { try { const nameEl = desc.querySelector('p.name[data-testid="productName"]'); const name = nameEl ? `"${nameEl.textContent.trim()}"` : '""'; const metaRoot = desc.querySelector('[data-testid="areaMetadataDropdown"]'); const setName = metaRoot?.querySelector('.display-text span')?.textContent?.trim() || ''; const rarity = (metaRoot?.querySelector('.expand-items li:nth-child(2)')?.textContent || '').trim(); const condition = desc.querySelector('p.condition[data-testid="txtItemCondition"]')?.textContent?.trim() || ''; const price = desc.querySelector('p.price span.checkout-price')?.textContent?.trim()?.replace(/\$/g, '') || ''; // Quantity isn't exposed in your snippet at checkout; default to 1. const qty = '1'; csv += [name, setName, rarity, condition, price, qty].join(',') + '\n'; } catch (e) { // continue on any odd row } } return csv; } function generateCartCSV_OldCart() { // Legacy cart (sellerWrapMarket) fallback let tables = Array.from($('.sellerWrapMarket')); if (!tables.length) return null; let csv = 'Name,Set,Rarity,Condition,Individual Price,Quantity\n'; for (let table of tables) { let $table = $(table); let rows = Array.from($table.find('table.sellerTable')); for (let row of rows) { let $row = $(row); const name = $row.find('.itemsContents h3').text().replace(/ *\([^)]*\) */g, '').trim(); const price = $row.find('.priceBox').text().trim().replace(/\$/g, ''); if (name) { // These legacy rows didn't surface set/rarity/condition consistently; fill what we have. csv += [`"${name}"`, '', '', '', price, '1'].join(',') + '\n'; } } } return csv; } function generateCartCSV() { // Try the new checkout DOM first; if not found, fall back to old cart DOM. return generateCartCSV_NewCheckout() || generateCartCSV_OldCart() || ''; } /* -------------------- export + clipboard -------------------- */ async function exportCsvToConsoleAndClipboard() { const onHistory = location.href.includes('/myaccount/orderhistory'); const onCartLike = location.href.includes('/cart') || location.href.includes('/checkout') || location.href.includes('/shoppingcart/review') || location.href.includes('cart.tcgplayer.com'); let parts = []; if (onHistory) { const historyCsv = generateOrderHistoryCSV(); if (historyCsv && historyCsv.split('\n').length > 1) { console.log('[TCG CSV] Order History CSV:\n', historyCsv); parts.push(historyCsv); } } if (onCartLike) { const cartCsv = generateCartCSV(); if (cartCsv && cartCsv.split('\n').length > 1) { console.log('[TCG CSV] Cart/Checkout CSV:\n', cartCsv); parts.push(cartCsv); } } if (!parts.length) { console.warn('[TCG CSV] No CSV data found for this page.'); window.alert('No CSV data found on this page.'); return; } const combined = parts.join('\n'); try { if (navigator.clipboard && window.isSecureContext) { await navigator.clipboard.writeText(combined); window.alert('CSV generated, logged to console, and copied to clipboard.'); } else { window.alert('CSV generated and logged to console.\n(Clipboard unavailable in this context.)'); } } catch (e) { console.warn('Clipboard write failed:', e); window.alert('CSV generated and logged to console.\n(Clipboard copy failed.)'); } } /* -------------------- button injection under “Place Order” -------------------- */ function findCheckoutButton() { // Typical class let btn = document.querySelector('section.cart-summary .checkout-btn') || document.querySelector('.checkout-btn'); // Fallback: match visible text "Place Order" if (!btn) { const spans = $$(document, '.tcg-standard-button__content span'); const match = spans.find(s => s.textContent.trim().toLowerCase() === 'place order'); if (match) btn = match.closest('button'); } // Extra fallback: sometimes testid is present if (!btn) { btn = document.querySelector('button[data-testid="btnPlaceOrder"]'); } return btn; } function injectExportButton() { const checkoutBtn = findCheckoutButton(); if (!checkoutBtn) return; if (document.querySelector('button[data-testid="btnExportCartHistoryCsv"]')) return; const exportRow = createButtonRow( 'Export Cart/History CSV', exportCsvToConsoleAndClipboard, 'btnExportCartHistoryCsv' ); // Insert directly UNDER the Place Order button checkoutBtn.insertAdjacentElement('afterend', exportRow); } function setupObserver() { const observer = new MutationObserver(injectExportButton); observer.observe(document.body, { childList: true, subtree: true }); } /* -------------------- init -------------------- */ window.addEventListener('load', () => { setupObserver(); injectExportButton(); }); })();