Blacklist & Warning for AutoDarts

Search for player names and display a warning message if found.

// ==UserScript==
// @name         Blacklist & Warning for AutoDarts 
// @namespace    Owl
// @version      0.1
// @description  Search for player names and display a warning message if found.
// @match        https://play.autodarts.io/*
// @run-at       document-idle
// @license      MIT
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    console.log("[Blacklist Script] Starting (EN) - File Import/Export...");

    let blacklistedPlayers = JSON.parse(localStorage.getItem('blacklistedPlayers')) || [];

    function savePlayerList() {
        localStorage.setItem('blacklistedPlayers', JSON.stringify(blacklistedPlayers));
    }

    // Popup variables
    let popupVisible = false;
    let popupContainer = null;

    // Menu item ID
    const MENU_ITEM_ID = 'autodarts-blacklist-menu-item';

    // --------------------------------------------------
    // A) DRAG LOGIC
    // --------------------------------------------------
    let isDragging = false;
    let offsetX = 0;
    let offsetY = 0;
    let hasConvertedCenter = false;

    function onDragMouseDown(e) {
        e.preventDefault();
        if (!hasConvertedCenter) {
            convertCenterToAbsolutePosition(popupContainer);
            hasConvertedCenter = true;
        }
        isDragging = true;
        offsetX = popupContainer.offsetLeft - e.clientX;
        offsetY = popupContainer.offsetTop - e.clientY;
    }

    function onDragMouseMove(e) {
        if (!isDragging) return;
        e.preventDefault();
        const newLeft = e.clientX + offsetX;
        const newTop = e.clientY + offsetY;
        popupContainer.style.left = newLeft + 'px';
        popupContainer.style.top = newTop + 'px';
    }

    function onDragMouseUp() {
        isDragging = false;
    }

    function convertCenterToAbsolutePosition(elem) {
        const rect = elem.getBoundingClientRect();
        elem.style.left = rect.left + 'px';
        elem.style.top = rect.top + 'px';
        elem.style.transform = 'none';
    }

    // --------------------------------------------------
    // B) CREATE POPUP
    // --------------------------------------------------
    function createPopup() {
        if (popupContainer) return;

        popupContainer = document.createElement('div');
        popupContainer.id = 'autodarts-blacklist-popup';

        Object.assign(popupContainer.style, {
            position: 'fixed',
            left: '50%',
            top: '50%',
            transform: 'translate(-50%, -50%)',

            padding: '20px',
            backgroundColor: '#1A202C', // dark gray
            color: '#E2E8F0',
            border: '1px solid #2D3748',
            borderRadius: '8px',
            boxShadow: '0 0 10px rgba(0,0,0,0.5)',
            zIndex: '99999',
            fontFamily: 'sans-serif',

            width: 'auto',
            minWidth: '300px',
            maxWidth: '80vw',
            maxHeight: '80vh',
            overflowY: 'auto',
            display: 'none'
        });

        // Drag bar
        const dragBar = document.createElement('div');
        dragBar.style.height = '20px';
        dragBar.style.cursor = 'move';
        dragBar.style.marginBottom = '10px';
        popupContainer.appendChild(dragBar);

        // Close "X" button
        const closeXButton = document.createElement('button');
        closeXButton.textContent = '×';
        Object.assign(closeXButton.style, {
            position: 'absolute',
            top: '4px',
            right: '8px',
            background: 'transparent',
            border: 'none',
            color: '#E2E8F0',
            fontSize: '20px',
            lineHeight: '20px',
            cursor: 'pointer'
        });
        closeXButton.addEventListener('click', () => {
            togglePopup(false);
        });
        popupContainer.appendChild(closeXButton);

        // Title
        const title = document.createElement('h2');
        title.textContent = 'BLACKLIST';
        title.style.marginTop = '0';
        title.style.fontSize = '1.4rem';
        title.style.fontWeight = 'bold';
        popupContainer.appendChild(title);

        // Input area
        const inputWrapper = document.createElement('div');
        inputWrapper.style.display = 'flex';
        inputWrapper.style.marginBottom = '10px';
        popupContainer.appendChild(inputWrapper);

        const input = document.createElement('input');
        input.type = 'text';
        input.placeholder = 'Enter new player name';
        Object.assign(input.style, {
            flex: '1',
            marginRight: '5px',
            padding: '4px 8px'
        });
        inputWrapper.appendChild(input);

        const addButton = document.createElement('button');
        addButton.textContent = 'Add';
        Object.assign(addButton.style, {
            padding: '4px 8px',
            cursor: 'pointer',
            backgroundColor: 'rgba(59, 182, 43, 1)',
            color: '#fff',
            border: 'none',
            borderRadius: '4px'
        });
        inputWrapper.appendChild(addButton);

        // UL list
        const listElement = document.createElement('ul');
        listElement.style.listStyle = 'none';
        listElement.style.paddingLeft = '0';
        popupContainer.appendChild(listElement);

        // Add function
        function addName() {
            const name = input.value.trim();
            const uppercaseName = name.toUpperCase();
            if (uppercaseName && !blacklistedPlayers.includes(uppercaseName)) {
                blacklistedPlayers.push(uppercaseName);
                savePlayerList();
                updateList(listElement);
            }
            input.value = '';
        }
        addButton.addEventListener('click', addName);
        input.addEventListener('keydown', (e) => {
            if (e.key === 'Enter') {
                e.preventDefault();
                addName();
            }
        });

        // Show the list initially
        updateList(listElement);

        // IMPORT/EXPORT section
        const importExportWrapper = document.createElement('div');
        importExportWrapper.style.borderTop = '1px solid #2D3748';
        importExportWrapper.style.paddingTop = '10px';
        importExportWrapper.style.marginTop = '10px';
        popupContainer.appendChild(importExportWrapper);

        // Export button
        const exportButton = document.createElement('button');
        exportButton.textContent = 'Export';
        Object.assign(exportButton.style, {
            padding: '4px 8px',
            cursor: 'pointer',
            marginRight: '10px',
            backgroundColor: '#2D3748',
            color: '#fff',
            border: 'none',
            borderRadius: '4px'
        });
        exportButton.addEventListener('click', () => {
            const data = JSON.stringify(blacklistedPlayers, null, 2);

            // 1) Copy to clipboard
            navigator.clipboard.writeText(data)
              .then(() => {
                alert('Blacklist has been copied to your clipboard.');
                // 2) Ask if user wants to save as file
                const saveFile = confirm('Do you want to save it as a JSON file?');
                if (saveFile) {
                    // Create a Blob, then force a download
                    const blob = new Blob([data], { type: 'application/json' });
                    const url = URL.createObjectURL(blob);
                    const a = document.createElement('a');
                    a.href = url;
                    a.download = 'blacklist.json';
                    document.body.appendChild(a);
                    a.click();
                    document.body.removeChild(a);
                    URL.revokeObjectURL(url);
                }
              })
              .catch(err => {
                alert('Failed to copy blacklist: ' + err);
              });
        });
        importExportWrapper.appendChild(exportButton);

        // Import button
        const importButton = document.createElement('button');
        importButton.textContent = 'Import';
        Object.assign(importButton.style, {
            padding: '4px 8px',
            cursor: 'pointer',
            backgroundColor: '#2D3748',
            color: '#fff',
            border: 'none',
            borderRadius: '4px'
        });
        importButton.addEventListener('click', () => {
            const choice = confirm('Would you like to import from file instead of JSON text?\nClick "OK" for file, "Cancel" for text prompt.');
            if (choice) {
                // Import from file
                importFromFile(listElement);
            } else {
                // Use prompt
                importFromPrompt(listElement);
            }
        });
        importExportWrapper.appendChild(importButton);

        document.body.appendChild(popupContainer);

        // Register drag events
        dragBar.addEventListener('mousedown', onDragMouseDown);
        document.addEventListener('mousemove', onDragMouseMove);
        document.addEventListener('mouseup', onDragMouseUp);
    }

    // ---- IMPORT FROM FILE ----
    function importFromFile(listElement) {
        const fileInput = document.createElement('input');
        fileInput.type = 'file';
        fileInput.accept = 'application/json,.json';
        fileInput.style.display = 'none';

        fileInput.addEventListener('change', function() {
            if (fileInput.files && fileInput.files[0]) {
                const file = fileInput.files[0];
                const reader = new FileReader();
                reader.onload = function(e) {
                    try {
                        const content = e.target.result;
                        const importedList = JSON.parse(content);
                        if (Array.isArray(importedList)) {
                            // Convert all to uppercase
                            importedList.forEach((val, idx) => {
                                importedList[idx] = val.toUpperCase();
                            });
                            blacklistedPlayers = importedList;
                            savePlayerList();
                            updateList(listElement);
                            checkPlayers();
                            alert('Blacklist imported successfully from file!');
                        } else {
                            alert('Invalid JSON data in file: Must be an array, e.g. ["Alice","Bob"].');
                        }
                    } catch (err) {
                        alert('Invalid JSON file: ' + err);
                    }
                };
                reader.readAsText(file);
            }
        });

        // "Click" the input programmatically
        document.body.appendChild(fileInput);
        fileInput.click();
        // Remove it afterwards to keep the DOM clean
        fileInput.parentNode.removeChild(fileInput);
    }

    // ---- IMPORT FROM PROMPT ----
    function importFromPrompt(listElement) {
        const inputData = prompt('Paste the JSON data for the blacklist:');
        if (inputData) {
            try {
                const importedList = JSON.parse(inputData);
                if (Array.isArray(importedList)) {
                    // Optional: convert all to uppercase
                    importedList.forEach((val, idx) => {
                        importedList[idx] = val.toUpperCase();
                    });
                    blacklistedPlayers = importedList;
                    savePlayerList();
                    updateList(listElement);
                    checkPlayers();
                    alert('Blacklist imported successfully!');
                } else {
                    alert('Invalid JSON data: Must be an array, e.g. ["Alice","Bob"].');
                }
            } catch (e) {
                alert('Invalid JSON: ' + e);
            }
        }
    }

    function updateList(listElement) {
        listElement.innerHTML = '';
        blacklistedPlayers.forEach(name => {
            const li = document.createElement('li');
            li.textContent = name;
            Object.assign(li.style, {
                display: 'flex',
                alignItems: 'center',
                justifyContent: 'space-between',
                padding: '4px 0'
            });

            const removeBtn = document.createElement('button');
            removeBtn.textContent = 'X';
            Object.assign(removeBtn.style, {
                marginLeft: '10px',
                backgroundColor: '#C53030',
                color: '#fff',
                border: 'none',
                borderRadius: '4px',
                padding: '2px 8px',
                cursor: 'pointer'
            });

            removeBtn.addEventListener('click', () => {
                blacklistedPlayers = blacklistedPlayers.filter(player => player !== name);
                savePlayerList();
                updateList(listElement);
                checkPlayers();
            });

            li.appendChild(removeBtn);
            listElement.appendChild(li);
        });
    }

    function togglePopup(forceOpen) {
        if (!popupContainer) {
            createPopup();
        }
        popupVisible = (typeof forceOpen === 'boolean') ? forceOpen : !popupVisible;
        popupContainer.style.display = popupVisible ? 'block' : 'none';
    }

    // --------------------------------------------------
    // WARNING AT THE TOP
    // --------------------------------------------------
    let warningDiv = null;

    function showWarning(text) {
        if (!warningDiv) {
            warningDiv = document.createElement('div');
            Object.assign(warningDiv.style, {
                position: 'fixed',
                top: '0',
                left: '0',
                width: '100%',
                backgroundColor: 'red',
                color: 'white',
                padding: '10px',
                textAlign: 'center',
                zIndex: '100000',
                fontSize: '16px'
            });
            document.body.appendChild(warningDiv);
        }
        warningDiv.textContent = `Attention: ${text}`;
    }

    function removeWarning() {
        if (warningDiv) {
            warningDiv.remove();
            warningDiv = null;
        }
    }

    function checkPlayers() {
        const playerTags = document.querySelectorAll('p.chakra-text.css-0');
        let foundNames = [];

        playerTags.forEach(tag => {
            const name = tag.textContent.trim().toUpperCase();
            if (blacklistedPlayers.includes(name)) {
                if (tag.style.backgroundColor !== 'red' || tag.style.color !== 'white') {
                    tag.style.backgroundColor = 'red';
                    tag.style.color = 'white';
                }
                foundNames.push(name);
            } else {
                if (tag.style.backgroundColor === 'red' || tag.style.color === 'white') {
                    tag.style.backgroundColor = '';
                    tag.style.color = '';
                }
            }
        });

        if (foundNames.length > 0) {
            showWarning(`Players found: ${foundNames.join(', ')}`);
        } else {
            removeWarning();
        }
    }

    // --------------------------------------------------
    // MENU ITEM
    // --------------------------------------------------
    function addBlacklistMenuItem(menuContainer) {
        console.log("[Blacklist Script] Adding the blacklist menu item...");

        let blacklistLink = document.getElementById(MENU_ITEM_ID);
        if (!blacklistLink) {
            blacklistLink = document.createElement('a');
            blacklistLink.id = MENU_ITEM_ID;
            blacklistLink.textContent = 'Blacklist';
            blacklistLink.className = 'chakra-button css-1nal3hj';
            blacklistLink.style.cursor = 'pointer';

            const icon = document.createElement('span');
            icon.className = 'chakra-button__icon css-1wh2kri';
            icon.style.marginRight = '0.5rem';
            icon.innerHTML = `
                <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"
                     viewBox="0 0 24 24" fill="currentColor">
                    <path d="M12 2C6.49 2 2 6.49 2 12s4.49
                    10 10 10 10-4.49 10-10S17.51
                    2 12 2zm3 14H9v-2h6v2zm2-4H7V8h10v4z"/>
                </svg>`;
            blacklistLink.prepend(icon);

            menuContainer.appendChild(blacklistLink);
            blacklistLink.addEventListener('click', () => {
                togglePopup();
            });
        }
    }

    // --------------------------------------------------
    // MUTATIONOBSERVER MIT DEBOUNCE
    // --------------------------------------------------
    let checkTimeout = null;
    function triggerCheckWithDebounce() {
        if (checkTimeout) {
            clearTimeout(checkTimeout);
        }
        checkTimeout = setTimeout(() => {
            checkPlayers();
            checkTimeout = null;
        }, 300);
    }

    const observer = new MutationObserver(() => {
        triggerCheckWithDebounce();
    });
    observer.observe(document.body, { childList: true, subtree: true });

    // --------------------------------------------------
    // MENU FIND + ADD
    // --------------------------------------------------
    const intervalId = setInterval(() => {
        const menuContainer = [...document.querySelectorAll('div.chakra-stack')]
          .find(div => div.querySelector('a[href="/"]') && div.querySelector('a[href="/lobbies"]'));

        if (menuContainer) {
            console.log("[Blacklist Script] Menu found:", menuContainer);
            addBlacklistMenuItem(menuContainer);
            clearInterval(intervalId);
        } else {
            console.log("[Blacklist Script] Menu not found yet. Trying again...");
        }
    }, 1000);

    // Start
    checkPlayers();

})();