flatpick

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.

Vous devrez installer une extension telle que Tampermonkey, Greasemonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Userscripts pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension de gestionnaire de script utilisateur pour installer ce script.

(J'ai déjà un gestionnaire de scripts utilisateur, laissez-moi l'installer !)

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

(J'ai déjà un gestionnaire de style utilisateur, laissez-moi l'installer!)

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