// ==UserScript==
// @name Save Image As...
// @description Allows you to save an image in different formats.
// @namespace Meica05GOD
// @version 2.4
// @license MIT
// @author GPT 4o
// @match *://*/*
// @grant GM_download
// @grant GM_xmlhttpRequest
// @run-at document-end
// ==/UserScript==
(function() {
'use strict';
const lang = (navigator.language || 'en').toLowerCase();
const isEs = lang.startsWith('es');
const L = {
menu: {
png: isEs ? 'Guardar como PNG' : 'Save as PNG',
jpg: isEs ? 'Guardar como JPG' : 'Save as JPG',
webp: isEs ? 'Guardar como WEBP' : 'Save as WEBP',
},
alert: {
httpError: status => isEs
? `Error HTTP ${status} al descargar la imagen.`
: `HTTP error ${status} downloading image.`,
networkError: isEs
? 'Error de red al descargar la imagen.'
: 'Network error downloading image.',
convertError: ext => isEs
? `No se pudo convertir la imagen a .${ext}`
: `Could not convert image to .${ext}`,
loadError: isEs
? 'Error cargando la imagen para conversión.'
: 'Error loading image for conversion.',
}
};
const menu = document.createElement('div');
Object.assign(menu.style, {
position: 'fixed',
background: '#fff',
border: '1px solid #ccc',
boxShadow: '2px 2px 5px rgba(0,0,0,0.2)',
zIndex: 2147483647,
padding: '5px 0',
display: 'none',
borderRadius: '4px',
minWidth: '150px',
fontFamily: 'sans-serif',
fontSize: '14px'
});
document.body.appendChild(menu);
const formats = [
{ key: 'png', mime: 'image/png', ext: 'png' },
{ key: 'jpg', mime: 'image/jpeg', ext: 'jpg' },
{ key: 'webp', mime: 'image/webp', ext: 'webp' }
];
formats.forEach(({ key, mime, ext }) => {
const item = document.createElement('div');
item.textContent = L.menu[key];
Object.assign(item.style, {
padding: '6px 12px',
cursor: 'pointer',
color: '#000',
opacity: '1',
pointerEvents: 'auto',
});
item.addEventListener('click', e => {
e.stopPropagation();
if (currentImgUrl) downloadAs(currentImgUrl, mime, ext);
hideMenu();
});
menu.appendChild(item);
});
let currentImgUrl = null;
document.addEventListener('contextmenu', function(e) {
const target = e.target;
let imgUrl = null;
const imgEl = target.closest('img');
if (imgEl && imgEl.src) {
imgUrl = imgEl.src;
} else {
const bg = getComputedStyle(target).getPropertyValue('background-image');
const m = bg.match(/url\(["']?(.*?)["']?\)/);
if (m) imgUrl = m[1];
}
if (e.shiftKey && imgUrl) {
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
currentImgUrl = imgUrl;
showMenu(e.clientX, e.clientY);
} else {
hideMenu();
}
}, true);
document.addEventListener('click', e => {
if (!menu.contains(e.target)) hideMenu();
}, true);
function showMenu(x, y) {
const mw = menu.offsetWidth, mh = menu.offsetHeight;
const ww = window.innerWidth, wh = window.innerHeight;
if (x + mw > ww) x = ww - mw - 5;
if (y + mh > wh) y = wh - mh - 5;
if (x < 0) x = 5;
if (y < 0) y = 5;
menu.style.left = x + 'px';
menu.style.top = y + 'px';
menu.style.display = 'block';
}
function hideMenu() {
menu.style.display = 'none';
currentImgUrl = null;
}
function downloadAs(url, mimeType, ext) {
GM_xmlhttpRequest({
method: 'GET',
url: url,
responseType: 'blob',
onload(resp) {
if (resp.status >= 200 && resp.status < 300) {
processBlob(resp.response, mimeType, ext);
} else {
alert(L.alert.httpError(resp.status));
}
},
onerror() {
alert(L.alert.networkError);
}
});
}
function processBlob(blob, mimeType, ext) {
const oUrl = URL.createObjectURL(blob);
const img = new Image();
img.onload = () => {
const c = document.createElement('canvas');
c.width = img.width; c.height = img.height;
c.getContext('2d').drawImage(img, 0, 0);
URL.revokeObjectURL(oUrl);
c.toBlob(cb => {
if (!cb) {
alert(L.alert.convertError(ext));
return;
}
const dUrl = URL.createObjectURL(cb);
GM_download({ url: dUrl, name: `image_${Date.now()}.${ext}`, saveAs: true });
setTimeout(() => URL.revokeObjectURL(dUrl), 10000);
}, mimeType);
};
img.onerror = () => {
URL.revokeObjectURL(oUrl);
alert(L.alert.loadError);
};
img.src = oUrl;
}
})();