// ==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);
}
}
})();