IdlePixel+ New Card Interface

Improved interface for opening new cards, receiving cards in trade & trading cards

目前为 2024-04-19 提交的版本。查看 最新版本

// ==UserScript==
// @name         IdlePixel+ New Card Interface
// @namespace    lbtechnology.info
// @version      1.2.2
// @description  Improved interface for opening new cards, receiving cards in trade & trading cards
// @author       Zlef
// @license      MIT
// @match        *://idle-pixel.com/login/play*
// @grant        none
// @icon         https://d1xsc8x7nc5q8t.cloudfront.net/images/tcg_back_50.png
// @require      https://greasyfork.org/scripts/441206-idlepixel/code/IdlePixel+.js?anticache=20220905
// ==/UserScript==

(function() {
    'use strict';

    class TCGRevamp extends IdlePixelPlusPlugin {
        constructor() {
            super("TCG Interface Changes", {
                about: {
                    name: GM_info.script.name,
                    version: GM_info.script.version,
                    author: GM_info.script.author,
                    description: GM_info.script.description
                }
            });

            this.showPopup = false;
            this.messageStart = "You got a"
            this.trade = false;
            this.card = "";
            this.refreshTCG = "";
            this.currentPopup = null;
            this.savedUsernamesTCG = null;
            this.previousTradeUsername = null;
            this.loadUsernames();


            this.overlay = document.createElement('div');
            this.overlay.id = 'newCardOverlay';
            this.overlay.style.position = 'fixed';
            this.overlay.style.top = '0';
            this.overlay.style.left = '0';
            this.overlay.style.width = '100%';
            this.overlay.style.height = '100%';
            this.overlay.style.backgroundColor = 'rgba(0, 0, 0, 0.7)';
            this.overlay.style.zIndex = '1000';
            this.overlay.style.display = 'flex';
            this.overlay.style.justifyContent = 'center';
            this.overlay.style.alignItems = 'center';
        }

        onLogin() {
            this.originalTcgGiveCard = Modals.open_tcg_give_card;

            Modals.open_tcg_give_card = (modal_id, card) => {
                if (!modal_id) {
                    this.newTradePopup(card);
                } else {
                    this.originalTcgGiveCard(modal_id, card);
                }
            };


            if (!CardData.data) {
                CardData.fetchData();
            }
            this.monitorRevealTCG();
        }

        saveUsernames(){
            const saveData = JSON.stringify({usernames: this.savedUsernamesTCG});
            localStorage.setItem(`savedUsernamesTCG`, saveData)

        }

        loadUsernames(){
            const savedUsernamesTCG = localStorage.getItem(`savedUsernamesTCG`)

            if (savedUsernamesTCG){
                this.savedUsernamesTCG = JSON.parse(savedUsernamesTCG).usernames
            } else {
                this.savedUsernamesTCG = [];
            }

        }

        monitorRevealTCG() {
            const originalWebSocketSend = WebSocket.prototype.send;
            const self = this;
            WebSocket.prototype.send = function(data) {
                try {
                    originalWebSocketSend.call(this, data);
                    if (data === 'REVEAL_TCG_CARD') {
                        self.showPopup = true;
                    }
                } catch (error) {
                    console.error('Error in overridden WebSocket send:', error);
                }
            };
        }

        onMessageReceived(data){
            const originalOpenImageModal = Modals.open_image_modal;
            const self = this;

            if (data.includes("OPEN_DIALOGUE")){
                Modals.open_image_modal = function(title, imgUrl, description, footerText, closeBtnText, anotherBtnText, isModalDismissible) {

                    if (description.includes("You were given a card from")) {

                        const usernameRegex = /You were given a card from (.*?)<br \/>/;
                        const cardRegex = /<span class='color-grey'>(.*?)<\/span>/;

                        const usernameMatch = description.match(usernameRegex);
                        const cardMatch = description.match(cardRegex);

                        let username = "";
                        let card = "";

                        if (usernameMatch && usernameMatch.length > 1) {
                            username = usernameMatch[1];
                        }

                        if (cardMatch && cardMatch.length > 1) {
                            self.card = cardMatch[1]
                        }


                        self.messageStart = `${username} sent you a`
                        self.trade = true;
                        self.showPopup = true;
                    } else {
                        originalOpenImageModal.call(this, title, imgUrl, description, footerText, closeBtnText, anotherBtnText, isModalDismissible);
                    }
                }
            }

            if (data.includes("REFRESH_TCG")){
                this.refreshTCG = data;
                if (this.showPopup){
                    if (this.trade){
                        this.getCardInfo();
                        // this.trade = false;
                        this.card = "";
                    } else {
                        this.getCardInfo();
                    }

                    this.showPopup = false;
                    this.messageStart = "You got a"
                }
            }
        }

        getCardInfoTrade(cardid) {
            const cardData = this.refreshTCG.replace('REFRESH_TCG=', '').split('~');
            // cardData looks like this: [ "21882", "tcg_thief_icon", "false", "21881", "tcg_raw_swordfish", "false", "21880", "tcg_raw_tuna", "false", "21805", … ]
            const index = cardData.indexOf(cardid);
            const isThisYourCard = [cardData[index], cardData[index + 1], cardData[index + 2]];

            const isHolo = isThisYourCard[2] === 'true';
            const cardHTML = CardData.getCardHTML(isThisYourCard[0], isThisYourCard[1], isHolo);
            return cardHTML;

        }

        updateUserListDisplay() {
            // Clear existing contents
            this.userListContainer.innerHTML = '';

            // Create table
            const table = document.createElement('table');
            table.className = 'table table-hover';

            // Table header
            const thead = document.createElement('thead');
            thead.innerHTML = '<tr><th scope="col">Saved users</th><th scope="col"></th></tr>';
            table.appendChild(thead);

            // Table body
            const tbody = document.createElement('tbody');
            table.appendChild(tbody);

            const usernames = this.savedUsernamesTCG;
            const minimumRows = 0; // Tested leaving blank rows, didn't like.
            const rowsToCreate = Math.max(minimumRows, usernames.length);

            // Everything else (I cba to comment atm)
            for (let i = 0; i < rowsToCreate; i++) {
                const tr = document.createElement('tr');
                const usernameCell = document.createElement('td');

                usernameCell.style.cursor = 'pointer';
                usernameCell.style.verticalAlign = 'middle';
                usernameCell.style.fontSize = '1.2em';
                usernameCell.addEventListener('click', () => {
                    const usernameInput = document.getElementById('recipientUsernameInput');
                    if (usernameInput) {
                        usernameInput.value = usernames[i];
                    }
                });

                const actionCell = document.createElement('td');
                actionCell.align = "right";
                actionCell.style.width = '80px';

                if (i < usernames.length) {
                    usernameCell.textContent = usernames[i];
                    const deleteButton = document.createElement('button');
                    deleteButton.textContent = 'Delete';
                    deleteButton.className = 'btn btn-danger btn-sm';
                    deleteButton.style.padding = '5px 10px';
                    deleteButton.style.height = 'auto';
                    deleteButton.onclick = () => this.deleteUsername(usernames[i]);
                    actionCell.appendChild(deleteButton);
                }

                tr.appendChild(usernameCell);
                tr.appendChild(actionCell);
                tbody.appendChild(tr);
            }

            this.userListContainer.appendChild(table);
        }

        deleteUsername(username) {
            const index = this.savedUsernamesTCG.indexOf(username);
            if (index !== -1) {
                this.savedUsernamesTCG.splice(index, 1);
                this.saveUsernames();
                this.updateUserListDisplay();
            }
        }


        newTradePopup(card) {
            const cardHTML = this.getCardInfoTrade(card).replace(/onclick='[^']+'/g, '');

            // tradePopup CSS
            const tradePopupStyles = `
			<style>
				#tradePopup {
					display: flex;
					flex-direction: column;
					align-items: center;
					width: 100%; /* Full width */
					max-width: 800px; /* Match the paint doodle's width */
					margin: 0 auto; /* Center the popup */
					background-color: #fff; /* White background */
					border-radius: 8px; /* Rounded corners */
					box-shadow: 0 0 10px rgba(0, 0, 0, 0.5); /* Box shadow for some depth */
					padding: 20px; /* Padding around the content */
					box-sizing: border-box; /* Ensures padding is included in width */
				}
				#tradePopup .tradePopup-row {
					display: flex;
					width: 100%;
				}
				#tradePopup .tradePopup-col {
					flex: 1; /* Each column takes up equal space */
					box-sizing: border-box; /* Ensures padding is included in width */
				}
				#tradePopup .tradePopup-card-container {
					flex: none;
					width: 35%;
				}
				#tradePopup .tradePopup-button-container {
					display: flex;
					justify-content: right;
					width: 100%; /* Full width of the popup */
				}
				#tradePopup button {
					padding: 10px 20px; /* Padding inside the buttons */
					cursor: pointer; /* Pointer cursor on hover */
					margin-right: 10px;
				}
                #userListContainer {
					max-height: 230px; /* Adjust to the desired max-height */
					overflow-y: auto; /* Enables scrollbar when content overflows */
				}
			</style>
			`;
            // Add the tradePopup styles to the document head
            document.head.insertAdjacentHTML('beforeend', tradePopupStyles);

            // Element setup
            const popupBox = document.createElement('div');
            popupBox.id = 'tradePopup';

            // Create a row to contain columns
            const rowDiv = document.createElement('div');
            rowDiv.className = 'tradePopup-row';
            popupBox.appendChild(rowDiv);

            // Column for the card
            const cardColDiv = document.createElement('div');
            cardColDiv.className = 'tradePopup-col tradePopup-card-container'; // Bootstrap column class
            rowDiv.appendChild(cardColDiv);

            // Column for the transaction form
            const formColDiv = document.createElement('div');
            formColDiv.className = 'tradePopup-col'; // Bootstrap column class
            rowDiv.appendChild(formColDiv);

            // Card container
            const cardContainer = document.createElement('div');
            cardContainer.innerHTML = cardHTML; // Insert the card HTML
            cardColDiv.appendChild(cardContainer);

            // Modal title
            const title = document.createElement('h5');
            title.textContent = "Who do you want to send this card to?";
            title.className = "modal-title";
            formColDiv.appendChild(title);

            // Input group for username and add user button
            const inputGroup = document.createElement('div');
            inputGroup.className = 'input-group mb-3';

            // Create input for username
            const usernameInput = document.createElement('input');
            usernameInput.type = 'text';
            usernameInput.className = 'form-control';
            usernameInput.id = 'recipientUsernameInput';
            usernameInput.placeholder = 'Enter username';
            if (this.previousTradeUsername){
                usernameInput.value = this.previousTradeUsername;
            }

            // Append input to input group
            inputGroup.appendChild(usernameInput);

            // Add User button next to the input
            const addUserButton = document.createElement('button');
            addUserButton.textContent = 'SAVE USER';
            addUserButton.className = 'btn btn-secondary';
            addUserButton.type = 'button';

            inputGroup.appendChild(addUserButton);
            formColDiv.appendChild(inputGroup);


            // User list container TODO
            this.userListContainer = document.createElement('div');
            this.userListContainer.id = 'userListContainer';
            formColDiv.appendChild(this.userListContainer);
            this.updateUserListDisplay();

            // Buttons
            const sendCardButton = document.createElement('button');
            sendCardButton.textContent = 'SEND CARD';
            sendCardButton.className = 'btn btn-primary';
            sendCardButton.type = 'button';

            const closeButton = document.createElement('button');
            closeButton.textContent = 'CLOSE';
            closeButton.className = 'btn btn-secondary';
            closeButton.type = 'button';

            // Buttons container
            const buttonContainer = document.createElement('div');
            buttonContainer.className = 'tradePopup-button-container';
            buttonContainer.appendChild(sendCardButton);
            buttonContainer.appendChild(closeButton);
            popupBox.appendChild(buttonContainer);

            // Define button actions
            const actions = [
                {
                    button: sendCardButton,
                    handler: () => {
                        const recipientUsername = usernameInput.value.trim();
                        this.previousTradeUsername = recipientUsername;
                        IdlePixelPlus.sendMessage(`GIVE_TCG_CARD=${recipientUsername}~${card}`);
                    }
                },
                {
                    button: closeButton,
                    handler: () => {
                        // Can be left blank
                    },
                    closeOnAction: true
                },
                {
                    button: addUserButton,
                    handler: () => {
                        const username = usernameInput.value;
                        if (username && !this.savedUsernamesTCG.includes(username)) {
                            this.savedUsernamesTCG.push(username);
                            this.saveUsernames();
                            this.updateUserListDisplay();
                        }
                    },
                    closeOnAction: false
                }
            ];


            this.launchPopup(popupBox, actions);
        }

        getCardInfo() {
            const cardData = this.refreshTCG.replace('REFRESH_TCG=', '').split('~');
            let isHolo = 'false';

            if (this.trade) {
                const cardName = this.card.replace(/ \(holo\)$/, '');
                const index = cardData.indexOf(cardName);
                if (index !== -1) {
                    const id = cardData[index - 1];
                    const nameKey = cardData[index];
                    const holo = cardData[index + 1];
                    isHolo = this.card.includes("(holo)") ? 'true' : holo;
                    this.displayNewCard(id, nameKey, isHolo);
                }
            } else {
                // Default to the first card for non trade
                if (cardData.length >= 3) {
                    const id = cardData[0];
                    const nameKey = cardData[1];
                    isHolo = cardData[2];
                    this.displayNewCard(id, nameKey, isHolo);
                }
            }
        }

        displayNewCard(cardId, cardNameKey, holo) {
            const cardName = cardNameKey.replace('tcg_', '').replace(/_/g, ' ').replace(" icon", "");
            const isHolo = holo === 'true';
            let bloodyVowels = "";

            const vowels = ['a', 'e', 'i', 'o', 'u'];
            if (vowels.some(vowel => cardName.toLowerCase().startsWith(vowel))) {
                bloodyVowels = "n";
            }

            const message = isHolo ? `${this.messageStart} holo ${cardName} card!` : `${this.messageStart}${bloodyVowels} ${cardName} card!`;

            const cardHTML = CardData.getCardHTML(cardId, cardNameKey, isHolo);

            this.newCardOverlay(message, cardHTML);

        }

        newCardOverlay(message, cardHTML) {
            // Element setup
            const popupBox = document.createElement('div');
            popupBox.id = 'newCardPopupBox';
            popupBox.style.width = '300px';
            popupBox.style.margin = '0 auto';
            popupBox.style.backgroundColor = '#fff';
            popupBox.style.boxShadow = '0 0 10px rgba(0, 0, 0, 0.5)';
            popupBox.style.borderRadius = '8px';
            popupBox.style.padding = '20px';
            popupBox.style.textAlign = 'center';

            const messageP = document.createElement('p');
            messageP.textContent = message;
            messageP.style.fontSize = '18px';
            messageP.style.fontWeight = 'bold';

            const cardContainer = document.createElement('div');
            cardContainer.innerHTML = cardHTML;
            cardContainer.firstChild.style.marginTop = '0px';

            const cardTitle = cardContainer.querySelector('.tcg-card-title');
            const cardInnerText = cardContainer.querySelector('.tcg-card-inner-text');
            if (cardTitle) {
                cardTitle.style.textAlign = 'left';
            }
            if (cardInnerText) {
                cardInnerText.style.textAlign = 'left';
            }

            const openAnotherButton = document.createElement('button');
            openAnotherButton.textContent = 'OPEN ANOTHER';
            openAnotherButton.style.padding = '10px 20px';
            openAnotherButton.style.fontSize = '16px';
            openAnotherButton.style.cursor = 'pointer';
            openAnotherButton.style.marginRight = '10px';

            const tcg_unknown = IdlePixelPlus.getVarOrDefault("tcg_unknown", 0, "int");
            openAnotherButton.disabled = tcg_unknown == 1; // was but never fully tested to understand wtf was going on (var might not update quickly enough, would explain why I went with 1?) <=1

            const closeButton = document.createElement('button');
            closeButton.textContent = 'CLOSE';
            closeButton.style.padding = '10px 20px';
            closeButton.style.fontSize = '16px';
            closeButton.style.cursor = 'pointer';

            // Define button actions
            const actions = [
                {
                    button: openAnotherButton,
                    handler: () => {
                        IdlePixelPlus.sendMessage("REVEAL_TCG_CARD");
                    }
                },
                {
                    button: closeButton,
                    handler: () => {
                    },
                    closeOnAction: true // True by default but stated for future reference
                }
            ];

            // Append elements to main popup
            popupBox.appendChild(messageP);
            popupBox.appendChild(cardContainer);
            if (!this.trade) {
                popupBox.appendChild(openAnotherButton);
            }
            popupBox.appendChild(closeButton);
            this.trade = false;

            this.launchPopup(popupBox, actions);
        }

        launchPopup(popup, actions) {

            if (this.currentPopup) {
                this.overlay.removeChild(this.currentPopup);
                this.currentPopup = null;
            }

            this.currentPopup = popup;

            this.overlay.appendChild(popup);
            document.body.appendChild(this.overlay);

            const adjustPopupPosition = () => {
                const viewportHeight = window.innerHeight;
                const popupHeight = popup.offsetHeight;
                const scrollOffset = window.pageYOffset || document.documentElement.scrollTop;
                const topPosition = (viewportHeight - popupHeight) / 2 + scrollOffset;
                popup.style.position = 'absolute';
                popup.style.top = `${topPosition > 0 ? topPosition : 0}px`;
            };

            adjustPopupPosition();
            // window.addEventListener('scroll', adjustPopupPosition);
            window.addEventListener('resize', adjustPopupPosition);


            // Apply actions (button clicks)
            actions.forEach(action => {
                const button = action.button;
                button.addEventListener('click', () => {
                    action.handler();
                    // Only remove the overlay if specified by the action
                    if (action.closeOnAction !== false) {
                        document.body.removeChild(this.overlay);
                        window.removeEventListener('resize', adjustPopupPosition);
                    }
                });
            });

            this.overlay.addEventListener('click', (event) => {
                if (event.target === this.overlay) {
                    document.body.removeChild(this.overlay);
                    window.removeEventListener('resize', adjustPopupPosition);
                }
            });

            popup.addEventListener('click', (event) => {
                event.stopPropagation();
            });

            this.oldpopup = popup.id;
        }

    }

    const plugin = new TCGRevamp();
    IdlePixelPlus.registerPlugin(plugin);

})();