TCGPlayer Cart/History to CSV in Console

Takes a TCGPlayer Cart/History and spits out a CSV

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==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();
  });
})();