Agar.io image to custom skin

Upload image for custom skin

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey, Greasemonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Userscripts.

Voor het installeren van scripts heb je een extensie nodig, zoals {tampermonkey_link:Tampermonkey}.

Voor het installeren van scripts heb je een gebruikersscriptbeheerder nodig.

(Ik heb al een user script manager, laat me het downloaden!)

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

(Ik heb al een beheerder - laat me doorgaan met de installatie!)

// ==UserScript==
// @name         Agar.io image to custom skin
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  Upload image for custom skin
// @author       New Jack 🕹️
// @match        agar.io/*
// @grant        none
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';
    const config = {
        containerWidth: '400px',
        maxPreviewSize: 180,
        buttonColor: '#54c800',
        buttonHoverColor: '#45a800',
        dropZoneHeight: '120px',
        dropZoneBorderColor: '#ccc',
        dropZoneActiveColor: '#54c800',
        agarioColors: {
            green: '#54c800',
            blue: '#0078fa',
            red: '#ff3d3d',
            yellow: '#ffcc00',
            background: 'rgb(255, 255, 255)',
            text: '#000000'
        }
    };
    const observer = new MutationObserver(mutations => {
        mutations.forEach(mutation => {
            if (mutation.addedNodes && mutation.addedNodes.length > 0) {
                for (let i = 0; i < mutation.addedNodes.length; i++) {
                    const node = mutation.addedNodes[i];
                    if (node.nodeType === Node.ELEMENT_NODE) {
                        if (node.querySelector('#skin-editor-canvas') || node.id === 'skin-editor-canvas') {
                            console.log('Skin editor detected!');
                            setTimeout(initializeImageUploader, 500);
                            return;
                        }
                    }
                }
            }
            if (mutation.removedNodes && mutation.removedNodes.length > 0) {
                for (let i = 0; i < mutation.removedNodes.length; i++) {
                    const node = mutation.removedNodes[i];
                    if (node.nodeType === Node.ELEMENT_NODE) {
                        if (node.querySelector && (
                            node.querySelector('#skin-editor-canvas') ||
                            node.id === 'skin-editor-canvas' ||
                            node.classList && (
                                node.classList.contains('skin-editor') ||
                                node.classList.contains('skin-editor-dialog')
                            )
                        )) {
                            console.log('Skin editor closed!');
                            removeImageUploader();
                            return;
                        }
                        const editorCloseIndicators = [
                            '.sk-app', '.editor-app', '.skin-editor',
                            '[data-v-skin-editor]', '#skin-editor'
                        ];
                        for (const selector of editorCloseIndicators) {
                            if (node.matches && node.matches(selector)) {
                                console.log('Possible skin editor close detected!');
                                setTimeout(checkIfEditorClosed, 100);
                                return;
                            }
                        }
                    }
                }
            }
        });
    });
    function startObserver() {
        if (document.body) {
            observer.observe(document.body, { childList: true, subtree: true });
            console.log('Observer started successfully');
        } else {
            console.log('Body not ready, waiting...');
            setTimeout(startObserver, 100);
        }
    }
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', startObserver);
    } else {
        startObserver();
    }
    function initializeImageUploader() {
        if (document.getElementById('custom-image-uploader-container')) {
            return;
        }
        console.log('Initializing image uploader...');
        const uploadContainer = createUploadContainer();
        document.body.appendChild(uploadContainer);
        positionUploaderNextToEditor(uploadContainer);
        console.log('Image uploader initialized successfully!');
    }
    function findControlsContainer() {
        const canvas = document.getElementById('skin-editor-canvas');
        if (!canvas) return null;
        const toolsSelector = '.tools, .colors, .controls, .editor-controls, .skin-editor-tools';
        const toolsContainer = document.querySelector(toolsSelector);
        if (toolsContainer) return toolsContainer;
        const skinEditorContainer = document.querySelector('.skin-editor, #skin-editor, [data-v-skin-editor]');
        if (skinEditorContainer) {
            const controls = skinEditorContainer.querySelector('.controls, .tools');
            if (controls) return controls;
            return skinEditorContainer;
        }
        let parent = canvas.parentElement;
        for (let i = 0; i < 5 && parent; i++) {
            if (parent.classList.contains('sk-app') ||
                parent.classList.contains('editor') ||
                parent.id === 'skin-editor' ||
                parent.classList.contains('skin-editor')) {
                return parent;
            }
            const controlElements = parent.querySelectorAll('button, .color, input, select');
            if (controlElements.length > 3) {
                return parent;
            }
            parent = parent.parentElement;
        }
        const canvasParent = canvas.parentElement;
        const fallbackContainer = document.createElement('div');
        fallbackContainer.id = 'skin-editor-controls-fallback';
        fallbackContainer.style.marginTop = '10px';
        canvasParent.appendChild(fallbackContainer);
        console.log('Created fallback container for skin editor controls');
        return fallbackContainer;
    }
    function createUploadContainer() {
        const container = document.createElement('div');
        container.id = 'custom-image-uploader-container';
        container.style.position = 'absolute';
        container.style.right = '50px';
        container.style.top = '70px';
        container.style.width = config.containerWidth;
        container.style.padding = '15px';
        container.style.backgroundColor = config.agarioColors.background;
        container.style.color = config.agarioColors.text;
        container.style.borderRadius = '8px';
        container.style.boxSizing = 'border-box';
        container.style.zIndex = '9999';
        container.style.boxShadow = '0 0 15px rgba(0, 0, 0, 0.5)';
        container.style.fontFamily = 'Ubuntu, Arial, sans-serif';
        container.style.border = '2px solid ' + config.agarioColors.green;
        const header = document.createElement('div');
        header.style.display = 'flex';
        header.style.justifyContent = 'space-between';
        header.style.alignItems = 'center';
        header.style.marginBottom = '15px';
        header.style.borderBottom = '1px solid ' + config.agarioColors.green;
        header.style.paddingBottom = '8px';
        const title = document.createElement('h3');
        title.textContent = 'New Jacks 🕹️ Skin Editor';
        title.style.margin = '0';
        title.style.color = config.agarioColors.blue;
        title.style.fontSize = '24px';
        title.style.fontWeight = 'bold';
        const toggleBtn = document.createElement('button');
        toggleBtn.textContent = '−';
        toggleBtn.style.backgroundColor = config.agarioColors.green;
        toggleBtn.style.border = 'none';
        toggleBtn.style.color = '#ccc';
        toggleBtn.style.fontSize = '18px';
        toggleBtn.style.cursor = 'pointer';
        toggleBtn.style.width = '24px';
        toggleBtn.style.height = '24px';
        toggleBtn.style.display = 'flex';
        toggleBtn.style.justifyContent = 'center';
        toggleBtn.style.alignItems = 'center';
        toggleBtn.style.borderRadius = '4px';
        const contentContainer = document.createElement('div');
        contentContainer.id = 'image-uploader-content';
        toggleBtn.addEventListener('click', () => {
            if (contentContainer.style.display === 'none') {
                contentContainer.style.display = 'block';
                toggleBtn.textContent = '−';
            } else {
                contentContainer.style.display = 'none';
                toggleBtn.textContent = '+';
            }
        });
        header.appendChild(title);
        header.appendChild(toggleBtn);
        container.appendChild(header);
        container.appendChild(contentContainer);
        const urlContainer = document.createElement('div');
        urlContainer.style.marginBottom = '12px';
        urlContainer.style.display = 'flex';
        const urlInput = document.createElement('input');
        urlInput.type = 'text';
        urlInput.placeholder = 'Enter image URL... ibb.co best';
        urlInput.style.flex = '1';
        urlInput.style.padding = '10px';
        urlInput.style.border = '1px solid #444';
        urlInput.style.backgroundColor = 'rgba (135, 135, 135, 0.3)';
        urlInput.style.color = '#000';
        urlInput.style.borderRadius = '4px';
        urlInput.style.marginRight = '5px';
        urlInput.style.fontSize = '18px';
        const urlButton = document.createElement('button');
        urlButton.textContent = 'Load';
        urlButton.style.padding = '10px';
        urlButton.style.backgroundColor = config.agarioColors.green;
        urlButton.style.color = 'black';
        urlButton.style.border = 'none';
        urlButton.style.borderRadius = '4px';
        urlButton.style.cursor = 'pointer';
        urlButton.style.fontWeight = 'bold';
        urlContainer.appendChild(urlInput);
        urlContainer.appendChild(urlButton);
        contentContainer.appendChild(urlContainer);
        const uploadButtons = document.createElement('div');
        uploadButtons.style.display = 'flex';
        uploadButtons.style.gap = '5px';
        uploadButtons.style.marginBottom = '12px';
        const fileInput = document.createElement('input');
        fileInput.type = 'file';
        fileInput.id = 'custom-skin-file';
        fileInput.accept = 'image/*';
        fileInput.style.display = 'none';
        const fileButton = document.createElement('button');
        fileButton.textContent = 'Upload From Computer';
        fileButton.style.flex = '1';
        fileButton.style.padding = '10px';
        fileButton.style.backgroundColor = config.agarioColors.blue;
        fileButton.style.color = 'white';
        fileButton.style.border = 'none';
        fileButton.style.borderRadius = '4px';
        fileButton.style.cursor = 'pointer';
        fileButton.style.fontWeight = 'bold';
        uploadButtons.appendChild(fileInput);
        uploadButtons.appendChild(fileButton);
        contentContainer.appendChild(uploadButtons);
        const dropZone = document.createElement('div');
        dropZone.id = 'custom-skin-drop-zone';
        dropZone.textContent = 'Drop Image Here';
        dropZone.style.height = config.dropZoneHeight;
        dropZone.style.border = `2px dashed ${config.agarioColors.yellow}`;
        dropZone.style.borderRadius = '4px';
        dropZone.style.display = 'flex';
        dropZone.style.flexDirection = 'column';
        dropZone.style.alignItems = 'center';
        dropZone.style.justifyContent = 'center';
        dropZone.style.color = '#ccc';
        dropZone.style.marginBottom = '12px';
        dropZone.style.fontSize = '16px';
        dropZone.style.backgroundColor = 'rgba(255, 255, 255, 0.5)';
        const dropIcon = document.createElement('div');
        dropIcon.innerHTML = '⬇️';
        dropIcon.style.fontSize = '24px';
        dropIcon.style.marginBottom = '8px';
        dropZone.appendChild(dropIcon);
        dropZone.appendChild(document.createTextNode('Drag & Drop Image Here'));
        contentContainer.appendChild(dropZone);
        const previewContainer = document.createElement('div');
        previewContainer.style.textAlign = 'center';
        previewContainer.style.marginTop = '12px';
        previewContainer.style.display = 'none';
        previewContainer.style.backgroundColor = 'rgba(255, 255, 255, 0.1)';
        previewContainer.style.padding = '10px';
        previewContainer.style.borderRadius = '4px';
        const previewLabel = document.createElement('div');
        previewLabel.textContent = 'Preview:';
        previewLabel.style.marginBottom = '8px';
        previewLabel.style.color = config.agarioColors.yellow;
        previewLabel.style.fontSize = '14px';
        previewLabel.style.fontWeight = 'bold';
        const previewImg = document.createElement('img');
        previewImg.id = 'custom-skin-preview';
        previewImg.style.maxWidth = '100%';
        previewImg.style.maxHeight = `${config.maxPreviewSize}px`;
        previewImg.style.border = '1px solid #444';
        previewImg.style.borderRadius = '50%';
        previewContainer.appendChild(previewLabel);
        previewContainer.appendChild(previewImg);
        contentContainer.appendChild(previewContainer);
        const helpText = document.createElement('div');
        helpText.style.fontSize = '20px';
        helpText.style.color = '#000';
        helpText.style.textShadow = '1px 1px 1px #444';
        helpText.style.marginTop = '16px';
        helpText.style.lineHeight = '1.4';
        helpText.style.padding = '8px';
        helpText.style.backgroundColor = 'rgba(0, 0, 0, 0.3)';
        helpText.style.borderRadius = '4px';
        helpText.style.borderLeft = '3px solid ' + config.agarioColors.yellow;
        helpText.innerHTML = `
            <b style="color:${config.agarioColors.blue}">Tips:</b><br>
            • If image doesn't appear clearly, try <a href="https://picwish.com/unblur-image-portrait" target="_blank" style="color:${config.agarioColors.blue};">unblurring it PicWish.com</a><br>
            • Use <a href="https://crop-circle.imageonline.co/" target="_blank" style="color:${config.agarioColors.blue};">Crop Circle Tool</a> for best results<br>
            • Simple designs with few colors work best<br>
            • The image will be centered and scaled
        `;
        contentContainer.appendChild(helpText);
        urlInput.addEventListener('keypress', (e) => {
            if (e.key === 'Enter') {
                loadImageFromUrl(urlInput.value);
            }
        });
        urlButton.addEventListener('click', () => {
            loadImageFromUrl(urlInput.value);
        });
        fileButton.addEventListener('click', () => {
            fileInput.click();
        });
        fileInput.addEventListener('change', () => {
            if (fileInput.files && fileInput.files[0]) {
                handleFileUpload(fileInput.files[0]);
            }
        });
        dropZone.addEventListener('dragover', (e) => {
            e.preventDefault();
            dropZone.style.borderColor = config.agarioColors.green;
            dropZone.style.backgroundColor = 'rgba(84, 200, 0, 0.1)';
        });
        dropZone.addEventListener('dragleave', () => {
            dropZone.style.borderColor = config.agarioColors.yellow;
            dropZone.style.backgroundColor = 'rgba(255, 255, 255, 0.05)';
        });
        dropZone.addEventListener('drop', (e) => {
            e.preventDefault();
            dropZone.style.borderColor = config.agarioColors.yellow;
            dropZone.style.backgroundColor = 'rgba(255, 255, 255, 0.05)';
            if (e.dataTransfer.files && e.dataTransfer.files[0]) {
                handleFileUpload(e.dataTransfer.files[0]);
            }
        });
        [fileButton, urlButton].forEach(button => {
            button.addEventListener('mouseover', () => {
                const originalColor = button.style.backgroundColor;
                const darkerColor = originalColor === config.agarioColors.green ?
                      config.buttonHoverColor :
                (originalColor === config.agarioColors.blue ? '#0060cc' : originalColor);
                button.style.backgroundColor = darkerColor;
            });
            button.addEventListener('mouseout', () => {
                button.style.backgroundColor = button === fileButton ?
                    config.agarioColors.blue : config.agarioColors.green;
            });
        });
        makeDraggable(container, header);
        return container;
    }
    function makeDraggable(element, handle) {
        let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
        handle.style.cursor = 'move';
        handle.onmousedown = dragMouseDown;
        function dragMouseDown(e) {
            e.preventDefault();
            pos3 = e.clientX;
            pos4 = e.clientY;
            document.onmouseup = closeDragElement;
            document.onmousemove = elementDrag;
        }
        function elementDrag(e) {
            e.preventDefault();
            pos1 = pos3 - e.clientX;
            pos2 = pos4 - e.clientY;
            pos3 = e.clientX;
            pos4 = e.clientY;
            element.style.top = (element.offsetTop - pos2) + "px";
            element.style.left = (element.offsetLeft - pos1) + "px";
            element.style.right = 'auto';
        }
        function closeDragElement() {
            document.onmouseup = null;
            document.onmousemove = null;
        }
    }
    function removeImageUploader() {
        const uploader = document.getElementById('custom-image-uploader-container');
        if (uploader) {
            uploader.remove();
            console.log('Image uploader removed');
        }
    }
    function checkIfEditorClosed() {
        const canvas = document.getElementById('skin-editor-canvas');
        if (!canvas) {
            removeImageUploader();
        }
    }
    function positionUploaderNextToEditor(uploader) {
        const canvas = document.getElementById('skin-editor-canvas');
        if (!canvas) return;
        const canvasRect = canvas.getBoundingClientRect();
        const editorWidth = canvas.clientWidth || canvas.offsetWidth;
        uploader.style.top = `${canvasRect.top}px`;
        uploader.style.left = 'auto';
        uploader.style.right = '20px';
    }
    function loadImageFromUrl(url) {
        if (!url) return;
        if (!url.match(/^https?:\/\/.+\..+/i)) {
            alert('Please enter a valid URL');
            return;
        }
        const img = new Image();
        img.crossOrigin = 'Anonymous';
        img.onload = () => {
            updatePreview(img.src);
            drawImageToCanvas(img);
        };
        img.onerror = () => {
            alert('Error loading image. The URL might be invalid or the server does not allow cross-origin requests.');
        };
        img.src = url;
    }
    function handleFileUpload(file) {
        if (!file || !file.type.startsWith('image/')) {
            alert('Please select a valid image file');
            return;
        }
        const reader = new FileReader();
        reader.onload = (e) => {
            const img = new Image();
            img.onload = () => {
                updatePreview(e.target.result);
                drawImageToCanvas(img);
            };
            img.src = e.target.result;
        };
        reader.readAsDataURL(file);
    }
    function updatePreview(src) {
        const preview = document.getElementById('custom-skin-preview');
        const previewContainer = preview.parentElement;
        preview.src = src;
        previewContainer.style.display = 'block';
        const successMsg = document.createElement('div');
        successMsg.textContent = 'Image loaded successfully!';
        successMsg.style.color = config.agarioColors.green;
        successMsg.style.fontWeight = 'bold';
        successMsg.style.marginTop = '5px';
        successMsg.style.fontSize = '12px';
        const existingMsg = previewContainer.querySelector('.success-msg');
        if (existingMsg) {
            previewContainer.removeChild(existingMsg);
        }
        successMsg.className = 'success-msg';
        previewContainer.appendChild(successMsg);
        setTimeout(() => {
            successMsg.style.transition = 'opacity 1s';
            successMsg.style.opacity = '0';
        }, 3000);
    }
    function drawImageToCanvas(img) {
        const canvas = document.getElementById('skin-editor-canvas');
        if (!canvas) {
            console.error('Skin editor canvas not found');
            return;
        }
        const ctx = canvas.getContext('2d');
        const originalWidth = canvas.width;
        const originalHeight = canvas.height;
        ctx.clearRect(0, 0, originalWidth, originalHeight);
        const scale = Math.min(
            originalWidth / img.width,
            originalHeight / img.height
        );
        const x = (originalWidth - img.width * scale) / 2;
        const y = (originalHeight - img.height * scale) / 2;
        const width = img.width * scale;
        const height = img.height * scale;
        ctx.drawImage(img, x, y, width, height);
        const changeEvent = new Event('change', { bubbles: true });
        canvas.dispatchEvent(changeEvent);
        try {
            if (window.drawApp && typeof window.drawApp.render === 'function') {
                window.drawApp.render(true);
            }
        } catch (e) {
            console.log('Internal draw method not available:', e);
        }
    }
})();