TCGPlayer Cart Buttons - Remove Duplicates, Save Expensive or Non-Direct For Later, and More!

Adds buttons to augment the tcgplayer shopping cart experience.

当前为 2024-05-06 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         TCGPlayer Cart Buttons - Remove Duplicates, Save Expensive or Non-Direct For Later, and More!
// @namespace    http://tampermonkey.net/
// @version      2024-05-06
// @description  Adds buttons to augment the tcgplayer shopping cart experience.
// @author       ganondorc
// @match        https://www.tcgplayer.com/cart
// @grant        none
// ==/UserScript==

(function () {
    'use strict';

    // Default values
    const defaultMinCards = 5;
    const defaultMaxPrice = 3;

    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;
    }

    function injectButtons() {
        const cartSummary = document.querySelector('section.cart-summary');
        const savedForLaterActions = document.querySelector('section.saved-for-later-actions');
        if (!cartSummary && !savedForLaterActions) {
            console.warn('Relevant sections not found.');
            return;
        }

        // Inject into cart summary section
        if (cartSummary) {
            const checkOutButton = cartSummary.querySelector('.checkout-btn');
            if (!checkOutButton) {
                console.warn('Checkout button not found.');
                return;
            }

            // Create the button container
            let buttonContainer = cartSummary.querySelector('.button-container');
            if (!buttonContainer) {
                buttonContainer = document.createElement('div');
                buttonContainer.className = 'button-container';
                cartSummary.insertBefore(buttonContainer, checkOutButton);
            }

            // Input for "save non-direct packages"
            if (!buttonContainer.querySelector('input[data-testid="inputMinCards"]')) {
                const inputRow = document.createElement('div');
                inputRow.className = 'button-row';
                inputRow.style.display = 'flex';
                inputRow.style.justifyContent = 'center';
                inputRow.style.flexDirection = 'column';

                const label = document.createElement('label');
                label.textContent = 'Min Cards';
                label.style.marginBottom = '5px';
                label.style.textAlign = 'center';

                const input = document.createElement('input');
                input.type = 'number';
                input.placeholder = 'Min Cards';
                input.min = '0';
                input.value = defaultMinCards;
                input.className = 'min-cards-input';
                input.style.height = '30px';
                input.dataset.testid = 'inputMinCards';

                inputRow.appendChild(label);
                inputRow.appendChild(input);
                buttonContainer.appendChild(inputRow);
            }

            // Save non-direct packages button
            if (!buttonContainer.querySelector('button[data-testid="btnSaveNonDirectPackages"]')) {
                const savePackagesButtonRow = createButtonRow(
                    'Save Non-Direct Packages',
                    () => {
                        const input = buttonContainer.querySelector('input[data-testid="inputMinCards"]');
                        const minCards = parseInt(input.value);
                        saveNonDirectPackages(minCards);
                    },
                    'btnSaveNonDirectPackages'
                );
                buttonContainer.appendChild(savePackagesButtonRow);
            }

            if (!buttonContainer.querySelector('button[data-testid="btnSaveExpensiveItems"]')) {
                // Create the input field row
                const inputRow = document.createElement('div');
                inputRow.className = 'button-row';
                inputRow.style.display = 'flex';
                inputRow.style.justifyContent = 'center';
                inputRow.style.flexDirection = 'column';

                const label = document.createElement('label');
                label.textContent = 'Max Price';
                label.style.marginBottom = '5px';
                label.style.textAlign = 'center';

                const input = document.createElement('input');
                input.type = 'number';
                input.placeholder = 'Max Price';
                input.step = '0.01';
                input.min = '0';
                input.value = defaultMaxPrice;
                input.className = 'max-price-input';
                input.style.height = '30px';

                inputRow.appendChild(label);
                inputRow.appendChild(input);
                buttonContainer.appendChild(inputRow);

                const saveExpensiveButtonRow = createButtonRow(
                    'Save Items Over Price',
                    () => {
                        const maxPrice = parseFloat(input.value);
                        if (isNaN(maxPrice)) {
                            window.alert('Please enter a valid maximum price.');
                        } else {
                            saveExpensiveItems(maxPrice);
                        }
                    },
                    'btnSaveExpensiveItems'
                );
                buttonContainer.appendChild(saveExpensiveButtonRow);
            }

            if (!buttonContainer.querySelector('button[data-testid="btnRemoveDuplicateCards"]')) {
                const removeDuplicatesButtonRow = createButtonRow(
                    'Remove Duplicate Cards',
                    removeDuplicateCards,
                    'btnRemoveDuplicateCards'
                );
                buttonContainer.appendChild(removeDuplicatesButtonRow);
            }
        }

        // Inject into saved for later actions section
        if (savedForLaterActions) {
            if (!savedForLaterActions.querySelector('button[data-testid="btnActuallyAddAllToCart"]')) {
                const actuallyAddAllButtonRow = createButtonRow(
                    'Actually Add All To Cart',
                    actuallyAddAllToCart,
                    'btnActuallyAddAllToCart'
                );
                savedForLaterActions.appendChild(actuallyAddAllButtonRow);
            }
        }
    }

    function isDirectPackage(packageElement) {
        return !packageElement.classList.contains('non-direct-package');
    }

    function saveItemsForPackage(packageElement) {
        const allItemsActions = packageElement.querySelector('.all-items-actions');
        if (allItemsActions) {
            const buttons = allItemsActions.querySelectorAll('button');
            const saveButton = Array.from(buttons).find(button =>
                button.textContent.includes("Save items")
            );

            if (saveButton) {
                console.log('Clicking Save items button for package:', packageElement);
                saveButton.click();
            } else {
                console.warn('Save items button not found for package:', packageElement);
            }
        } else {
            console.warn('all-items-actions div not found for package:', packageElement);
        }
    }

    function saveNonDirectPackages(minCards = defaultMinCards) {
        const packages = document.querySelectorAll('section.tab-content.non-direct-package');
        let savedPackagesCount = 0;
        packages.forEach(packageElement => {
            const items = packageElement.querySelectorAll('.package-item');

            // Only save packages with at least the specified minimum number of items
            if (!isDirectPackage(packageElement) && items.length < minCards) {
                saveItemsForPackage(packageElement);
                savedPackagesCount++;
            }
        });
        window.alert(`Saved items for ${savedPackagesCount} non-direct package(s) with under ${minCards} cards.`);
    }

    function saveExpensiveItems(maxPrice = defaultMaxPrice) {
        const stackedSections = document.querySelectorAll('section.stacked-content');
        let savedItemsCount = 0;
        stackedSections.forEach(sectionElement => {
            const items = sectionElement.querySelectorAll('.package-item');

            items.forEach(item => {
                const priceElement = item.querySelector('.price');
                if (priceElement) {
                    const priceText = priceElement.textContent.trim().replace('$', '');
                    const price = parseFloat(priceText);

                    if (price > maxPrice) {
                        const saveButton = item.querySelector('button.save-for-later');
                        if (saveButton) {
                            saveButton.click();
                            savedItemsCount++;
                        } else {
                            console.warn('Save for later button not found for item:', item);
                        }
                    }
                } else {
                    console.warn('Price element not found for item:', item);
                }
            });
        });
        window.alert(`Saved ${savedItemsCount} item(s) over $${maxPrice}.`);
    }

    function removeDuplicateCards() {
        const stackedSections = document.querySelectorAll('section.stacked-content');
        const itemMap = new Map();
        let removedItemsCount = 0;

        stackedSections.forEach(sectionElement => {
            const items = sectionElement.querySelectorAll('.package-item');

            items.forEach(item => {
                const nameElement = item.querySelector('[data-testid="productName"]');
                const priceElement = item.querySelector('.price');
                if (!nameElement || !priceElement) {
                    console.warn('Name or price element not found for item:', item);
                    return;
                }

                const name = nameElement.textContent.trim();
                const price = parseFloat(priceElement.textContent.trim().replace('$', ''));

                if (!itemMap.has(name)) {
                    itemMap.set(name, [{ item, price }]);
                } else {
                    itemMap.get(name).push({ item, price });
                }
            });
        });

        itemMap.forEach(itemArray => {
            itemArray.sort((a, b) => a.price - b.price);
            const selectedItem = itemArray[0];

            itemArray.forEach(({ item, price }, index) => {
                if (index > 0) {
                    const removeButton = item.querySelector('button.remove');
                    if (removeButton) {
                        removeButton.click();
                        removedItemsCount++;
                    }
                } else {
                    // Ensure the selected item's quantity is 1
                    const quantitySelect = item.querySelector('select[aria-label*="cart quantity"]');
                    if (quantitySelect) {
                        quantitySelect.value = '1';
                    }
                }
            });
        });

        window.alert(`Removed ${removedItemsCount} duplicate card(s).`);
    }

    function actuallyAddAllToCart() {
        const savedItems = document.querySelectorAll('section.saved-item');
        let addedItemsCount = 0;

        savedItems.forEach(item => {
            const addToCartButton = item.querySelector('button[data-testid="saveForLaterAddToCart"]');

            if (addToCartButton) {
                addToCartButton.click();
                addedItemsCount++;
            } else {
                console.warn('Add to Cart button not found for saved item:', item);
            }
        });

        window.alert(`Added ${addedItemsCount} item(s) to the cart.`);
    }

    function setupObserver() {
        const observer = new MutationObserver(() => {
            injectButtons();
        });

        observer.observe(document.body, { childList: true, subtree: true });
    }

    window.addEventListener('load', () => {
        setupObserver();
        injectButtons(); // Initial injection
    });
})();