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 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 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 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
    });
})();