Загрузка данных
// ==UserScript==
// @name Avatar Builder+++ (Con soporte GIF)
// @namespace avatar builder
// @version 2.7.1
// @description Adds an upload image input to the avatar builder with performance improvements
// @author gatogamer123xd
// @license MIT
// @match https://*.drawaria.online/avatar/builder/
// @require https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js
// @icon https://www.google.com/s2/favicons?domain=drawaria.online
// @grant none
// ==/UserScript==
(($) => {
'use strict';
// Función auxiliar para convertir y comprimir realmente a JPEG usando un Canvas
const convertToJpeg = (imgElement) => {
return new Promise((resolve) => {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.width = imgElement.width;
canvas.height = imgElement.height;
// Fondo blanco por si la imagen original tiene transparencias (PNG/WEBP)
ctx.fillStyle = '#FFFFFF';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(imgElement, 0, 0);
// Exporta a JPEG real con una calidad óptima del 90%
resolve(canvas.toDataURL('image/jpeg', 0.9));
});
};
const setupAvatarUpload = () => {
// Evita duplicados en la interfaz
if ($('#imageInput').length) return;
const $header = $('header');
if (!$header.length) return;
// Inyecta el botón y el input oculto de forma limpia
$header.append(`
<label class="Button" id="uploadBtn" for="imageInput" style="cursor: pointer; user-select: none;">Upload Image</label>
<input style="display:none" id="imageInput" type="file" accept="image/jpeg, image/png, image/webp, image/gif">
`);
const $imageInput = $('#imageInput');
const $labelButton = $('#uploadBtn');
// Función centralizada para enviar la imagen al servidor
const sendImageToServer = (uploadedImage) => {
$labelButton.text('Uploading...').css('pointer-events', 'none');
const targetUrl = window.LOGGEDIN ? '/saveavatar' : '/uploadavatarimage';
const payload = {
'avatarsave_builder': JSON.stringify(window.ACCOUNT_AVATARSAVE || {}),
'imagedata': uploadedImage,
'fromeditor': true
};
$.ajax({
url: targetUrl,
type: 'POST',
data: payload,
xhr: () => {
const xhr = new window.XMLHttpRequest();
xhr.upload.addEventListener('progress', (evt) => {
if (evt.lengthComputable) {
const percent = ((evt.loaded / evt.total) * 100).toFixed(0);
$labelButton.css(
'background',
`linear-gradient(180deg, #f6f9fc 85%, transparent 15%), linear-gradient(90deg, #3ad73d ${percent}%, #808386 0%)`
);
}
}, false);
return xhr;
}
})
.done(async (data) => {
$labelButton.text('Saving...').css({ 'background': '', 'pointer-events': 'none' });
try {
// Limpieza de caché asíncrona de la imagen guardada
const cacheUrl = `${window.location.origin}/avatar/cache/${data}.jpg`;
await fetch(cacheUrl, { method: 'GET', mode: 'cors', cache: 'reload' });
$labelButton.text('Save OK!');
window.location.href = window.location.origin;
} catch (err) {
window.location.href = window.location.origin;
}
})
.fail((_jqXHR, _textStatus, errorThrown) => {
// Restaurar botón si hay un error en el servidor
$labelButton.text('Upload Image').css({ 'background': '', 'pointer-events': 'auto' });
$imageInput.val('');
alert(`Upload failed: ${errorThrown || 'Unknown error'}`);
});
};
$imageInput.on('change', (event) => {
const file = event.target.files[0];
if (!file) return; // Seguridad
const reader = new FileReader();
reader.onload = () => {
// VERIFICACIÓN CLAVE: Si es un GIF, aplicamos el truco del segundo script
if (file.type === 'image/gif') {
// Reemplazamos el MIME type para engañar al servidor, pero conservamos los frames del GIF
const uploadedImage = reader.result.replace(/^data:[^;]+;/, 'data:image/jpeg;');
sendImageToServer(uploadedImage);
}
// Si es PNG, JPG, o WEBP, usamos el Canvas para comprimir y poner fondo blanco
else {
const img = new Image();
img.onload = async () => {
const uploadedImage = await convertToJpeg(img);
sendImageToServer(uploadedImage);
};
img.src = reader.result;
}
};
reader.readAsDataURL(file);
});
};
// Comprobación inicial rápida
const init = () => {
if ($('main').length && $('header').length) {
setupAvatarUpload();
return true;
}
return false;
};
// Si la página aún no ha cargado los elementos, activa el Observer de forma controlada
if (!init()) {
const observer = new MutationObserver((_mutations, obs) => {
if (init()) {
obs.disconnect(); // Se apaga inmediatamente tras cumplir su función
}
});
observer.observe(document.documentElement, { childList: true, subtree: true });
}
})(window.jQuery || jQuery);