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