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.

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey, Greasemonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да инсталирате разширение, като например Tampermonkey .

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Userscripts.

За да инсталирате скрипта, трябва да инсталирате разширение като Tampermonkey.

За да инсталирате този скрипт, трябва да имате инсталиран скриптов мениджър.

(Вече имам скриптов мениджър, искам да го инсталирам!)

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

(Вече имам инсталиран мениджър на стиловете, искам да го инсталирам!)

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