// ==UserScript==
// @name flatpick
// @namespace https://greasyfork.org/en/users/1508660-frrzyriq
// @version 1.1
// @description Flatpick is a userscript to enable download SVG and copy SVG on flaticon.com for free user it's open the premium button. Not affiliated with flaticon. Use at your own risk.
// @author frrzyriq
// @match https://flaticon.com
// @match https://www.flaticon.com/*
// @icon https://media.flaticon.com/dist/min/img/favicon.ico
// @grant none
// @run-at document-end
// @noframes
// @copyright 2025, frrzyriq (https://greasyfork.org/en/users/1508660-frrzyriq)
// @license MIT
// ==/UserScript==
(function () {
'use strict';
const FLATICON_PAYLOAD = 'MGUxODE1MDQwMzUzNGM0NDU3MTQwNTRmMDUwNzQxMTIwNTAyMWIwNzRkMGMwMTRkNGQxYzQ0MGYwNjFkMDg1NjFiMGExZTA4NDMxMjAyMTc0NjU5MDI0NDVjMDYxODEzMGUxZDE1MTgwMDFhMGQwMjFkMGE='
const claves = "66 6C 61 74 70 69 63 6B 20 63 72 61 63 6B 20 66 6C 61 74 69 63 6F 6E 20 62 79 20 66 72 72 7A 79 72 69 71".split(" ").map(h => parseInt(h, 16).toString(16).padStart(2, '0'));
const inspect = () => {
return atob(FLATICON_PAYLOAD).match(/.{1,2}/g).map((h, i) => String.fromCharCode(parseInt(h, 16) ^ parseInt(claves[i % claves.length], 16))).join("");
}
const watchAttrChange = (selector, callbackFun) => {
const MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver;
const observer = new MutationObserver((mutations) => {
callbackFun(mutations);
});
setInterval(() => {
const watchSelector = document.querySelector(selector);
if (watchSelector && !watchSelector.classList.contains('hidden')) {
observer.observe(watchSelector, { attributes: true });
}
setTimeout(() => {
callbackFun([]);
}, 500);
}, 100);
};
const watchElementChange = (selector, callbackFun) => {
const MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver;
const observer = new MutationObserver((mutations) => {
callbackFun(mutations);
});
observer.observe(document.querySelector(selector), { childList: true, subtree: true });
};
const watchMouseEnter = (selector, callbackFun) => {
const elements = document.querySelectorAll(selector);
elements.forEach((el) => {
el.addEventListener('mouseenter', (event) => {
callbackFun(event);
});
});
};
const revokeIcon = async (iconId) => {
const req = await fetch(
inspect().replace(':id', iconId)
);
const data = await req.json();
const raw = await fetch(data.url);
return raw;
}
const downloadSVG = async (iconId) => {
const data = await revokeIcon(iconId);
const blob = await data.blob();
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.style.display = 'none';
a.href = url;
a.download = `${iconId}.svg`;
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(url);
}
const copySVG = async (iconId) => {
const data = await revokeIcon(iconId);
const svgText = await data.text();
navigator.clipboard.writeText(svgText).then(() => {
alert('SVG copied to clipboard');
});
}
const removePremIcon = (element, color = 2) => {
const premIcon = element.querySelector('.icon.icon--crown-filled');
element.classList.remove('btn-warning');
switch (color) {
case 1:
element.classList.add('bj-button--green');
break;
case 2:
element.classList.add('bj-button--secondary');
break;
case 3:
element.classList.add('bj-button--primary');
break;
default:
element.classList.add('btn-warning');
}
premIcon?.remove();
}
const removeSVGpremDownloadOverlayHover = (element) => {
element.classList.add('fsd-attached');
const iconId = parseInt(element.getAttribute('data-id'), 10);
const ctxm = element.querySelector('.icon--item__context-menu');
const downloadOpt = ctxm.lastElementChild;
const svgDownloadBtn = downloadOpt.querySelector('.GA_CM_download-svg>button');
svgDownloadBtn.onclick = () => downloadSVG(iconId);
removePremIcon(svgDownloadBtn, 3);
}
const removeSVGandCopyPremDownloadOverlay = () => {
const downloadSection = document.querySelector('section#detail');
const iconId = parseInt(downloadSection.getAttribute('data-elementid'), 10);
const downloadBtn = downloadSection.querySelector('.btn-svg.btn,.btn-svg');
if (downloadBtn) {
removePremIcon(downloadBtn, 3);
downloadBtn.onclick = () => downloadSVG(iconId);
}
const copySvgBtn = downloadSection.querySelector('.btn-copy>.copysvg--button');
if (copySvgBtn) {
removePremIcon(copySvgBtn, 2);
copySvgBtn.onclick = () => copySVG(iconId);
}
}
const initFlatpick = () => {
watchMouseEnter('li.icon--item:not(.fsd-attached)', (event) => {
removeSVGpremDownloadOverlayHover(event.currentTarget);
});
watchAttrChange('#detail-overlay', (mutations) => {
mutations.filter(({ attributeName }) => attributeName === 'class').forEach(({ target }) => {
if (target.classList.contains('ready')) {
watchElementChange('#detail-content', (mutations) => {
removeSVGandCopyPremDownloadOverlay()
});
}
});
if (mutations.length === 0 && document.querySelector('section#detail') !== null) {
removeSVGandCopyPremDownloadOverlay();
}
});
}
const alertLoginRequired = () => {
const buttonLogin = document.querySelector('a.btn-login');
if (!buttonLogin) return;
const alertDiv = document.createElement('div');
Object.assign(alertDiv.style, {
width: 'fit-content',
position: 'fixed',
display: 'flex',
alignItems: 'center',
gap: '6px',
top: '80px',
right: '10px',
backgroundColor: '#f44336',
color: 'white',
padding: '12px 15px',
zIndex: '9999',
borderRadius: '6px',
});
alertDiv.innerHTML = `<p style="margin:0;"><b>Flatpick:</b> <i>Please log in to use this script.</i></p>`;
const cloneLogin = buttonLogin.cloneNode(true);
alertDiv.appendChild(cloneLogin);
const closeBtn = document.createElement('button');
closeBtn.className = 'btn-close-flatpick';
closeBtn.innerHTML = `
<svg xmlns="http://www.w3.org/2000/svg" width="24" viewBox="0 0 640 640" fill="#fff"><!--!Font Awesome Free v7.0.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M183.1 137.4C170.6 124.9 150.3 124.9 137.8 137.4C125.3 149.9 125.3 170.2 137.8 182.7L275.2 320L137.9 457.4C125.4 469.9 125.4 490.2 137.9 502.7C150.4 515.2 170.7 515.2 183.2 502.7L320.5 365.3L457.9 502.6C470.4 515.1 490.7 515.1 503.2 502.6C515.7 490.1 515.7 469.8 503.2 457.3L365.8 320L503.1 182.6C515.6 170.1 515.6 149.8 503.1 137.3C490.6 124.8 470.3 124.8 457.8 137.3L320.5 274.7L183.1 137.4z"/></svg>`;
closeBtn.onclick = () => alertDiv.remove();
alertDiv.appendChild(closeBtn);
if (!document.getElementById('flatpick-alert-style')) {
const style = document.createElement('style');
style.id = 'flatpick-alert-style';
style.textContent = `
.btn-close-flatpick {
all: unset;
border-radius:24px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
`;
document.head.appendChild(style);
}
document.body.appendChild(alertDiv);
};
if (window.USER_REGISTERED) {
window.addEventListener('popstate', function (event) {
initFlatpick();
});
document.addEventListener("visibilitychange", () => {
initFlatpick();
});
initFlatpick();
} else {
alertLoginRequired();
}
})();