Detecta y desencripta códigos/URL Base64 y los abre.
// ==UserScript==
// @name B64 - URL Decoder (by Bluembee)
// @author Bluembee
// @namespace https://base64-decoder
// @version 1.3
// @description Detecta y desencripta códigos/URL Base64 y los abre.
// @match *://*/*
// @match https://www.facebook.com/*
// @match https://www.identi.io/*
// @match https://www.x.com/*
// @grant none
// @run-at document-idle
// @icon https://pr.gg/base64/images/appicon192.png
// @license MIT
// ==/UserScript==
(function () {
'use strict';
/* ------------------ Sitios Objetivos ------------------ */
const ALLOWED_DOMAINS = [
'facebook.com',
'identi.io',
'identi.li',
'twitter.com',
'x.com'
];
function isAllowedSite() {
const host = location.hostname;
return ALLOWED_DOMAINS.some(d =>
host === d || host.endsWith('.' + d)
);
}
if (!isAllowedSite()) return;
let tooltip = null;
let lastDecoded = null;
/* ------------------ Base64 Herramientas ------------------ */
function looksLikeBase64(str) {
return /^[A-Za-z0-9+/=_-]{16,}$/.test(str);
}
function decodeOnce(str) {
try {
const fixed = str.replace(/-/g, '+').replace(/_/g, '/');
return decodeURIComponent(escape(atob(fixed)));
} catch {
return null;
}
}
function multiDecode(str, max = 3) {
let current = str;
let layers = 0;
for (let i = 0; i < max; i++) {
const decoded = decodeOnce(current);
if (!decoded || decoded === current) break;
current = decoded;
layers++;
}
return layers > 0 ? { url: current, layers } : null;
}
/* ------------------ Ventana informativa ------------------ */
function isDarkMode() {
return window.matchMedia &&
window.matchMedia('(prefers-color-scheme: dark)').matches;
}
function removeTooltip() {
if (tooltip) {
tooltip.remove();
tooltip = null;
}
lastDecoded = null;
}
function showTooltip(info, x, y) {
removeTooltip();
const dark = isDarkMode();
tooltip = document.createElement('div');
tooltip.style.cssText = `
position: fixed;
left: ${x + 12}px;
top: ${y + 12}px;
max-width: 460px;
padding: 8px 10px;
font-family: "Segoe UI", system-ui, sans-serif;
font-size: 12px;
line-height: 1.4;
border-radius: 8px;
backdrop-filter: blur(10px);
pointer-events: none;
z-index: 999999;
word-break: break-all;
background: ${dark ? 'rgba(32,32,32,0.88)' : 'rgba(255,255,255,0.88)'};
color: ${dark ? '#e6e6e6' : '#222'};
border: 1px solid ${dark ? '#444' : '#d0d0d0'};
box-shadow: 0 4px 16px rgba(0,0,0,.25);
`;
tooltip.innerHTML = `
<div style="font-weight:600; margin-bottom:4px;">
🔗 Enlace (Base64 x${info.layers})
</div>
<div>${info.url}</div>
<div style="margin-top:6px; opacity:.7;">
Ctrl + Alt + O para abrir
</div>
`;
document.body.appendChild(tooltip);
lastDecoded = info.url;
}
/* ------------------ Detección Seleccionada ------------------ */
document.addEventListener('selectionchange', () => {
const sel = window.getSelection();
if (!sel || sel.rangeCount === 0) {
removeTooltip();
return;
}
const text = sel.toString().trim();
if (!text || !looksLikeBase64(text)) {
removeTooltip();
return;
}
const decodedInfo = multiDecode(text);
if (!decodedInfo || !/^https?:\/\//i.test(decodedInfo.url)) {
removeTooltip();
return;
}
const rect = sel.getRangeAt(0).getBoundingClientRect();
showTooltip(decodedInfo, rect.right, rect.bottom);
});
document.addEventListener('mousedown', removeTooltip);
/* ------------------ Abrir enlace ------------------ */
document.addEventListener('keydown', e => {
if (!(e.ctrlKey && e.altKey && e.key.toLowerCase() === 'o')) return;
if (!lastDecoded) return;
const url = lastDecoded;
removeTooltip();
const w = window.open(url, '_blank');
if (w) w.opener = null;
});
})();