Descargador de Scripts de Usuario Greasyfork

Agrega un botón flotante para descargar todos los scripts de un usuario de Greasy Fork, utilizando GM_xmlhttpRequest para evitar CORS y delays para evitar bloqueos del navegador, con saneamiento de nombres de archivo.

Від 05.08.2025. Дивіться остання версія.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(У мене вже є менеджер скриптів, дайте мені встановити його!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         Descargador de Scripts de Usuario Greasyfork
// @namespace    http://tampermonkey.net/
// @version      1.1
// @description  Agrega un botón flotante para descargar todos los scripts de un usuario de Greasy Fork, utilizando GM_xmlhttpRequest para evitar CORS y delays para evitar bloqueos del navegador, con saneamiento de nombres de archivo.
// @author       YouTubeDrawaria
// @match        https://greasyfork.org/es/users/*
// @match        https://greasyfork.org/*/users/*
// @match        https://greasyfork.org/es/scripts/by-site/drawaria.online*
// @match        https://greasyfork.org/es/scripts/by-site/*
// @match        https://greasyfork.org/es/scripts*
// @match        https://greasyfork.org/*/scripts/by-site/drawaria.online*
// @match        https://greasyfork.org/*/scripts/by-site/*
// @match        https://greasyfork.org/*/scripts*
// @grant        GM_addStyle
// @grant        GM_xmlhttpRequest
// @connect      greasyfork.org
// @connect      update.greasyfork.org
// @license      MIT
// @icon         https://drawaria.online/avatar/cache/86e33830-86ea-11ec-8553-bff27824cf71.jpg
// ==/UserScript==

(function() {
    'use strict';

    // Función de descarga robusta usando GM_xmlhttpRequest para obtener el blob
    async function downloadFileRobustly({ url, filename }) {
        return new Promise((resolve, reject) => {
            GM_xmlhttpRequest({
                method: "GET",
                url: url,
                responseType: "blob", // Importante: pedir el contenido como blob
                onload: function(response) {
                    if (response.status === 200) {
                        const blob = response.response;
                        const objUrl = URL.createObjectURL(blob);

                        const a = document.createElement('a');
                        a.href = objUrl;
                        a.download = filename; // El nombre ya viene limpio y con extensión
                        document.body.appendChild(a); // Necesario para Firefox
                        a.click();
                        a.remove(); // Limpiar el elemento <a> del DOM
                        URL.revokeObjectURL(objUrl); // Liberar la memoria del objeto URL

                        resolve();
                    } else {
                        reject(new Error(`Failed to fetch ${url}: Status ${response.status} - ${response.statusText}`));
                    }
                },
                onerror: function(error) {
                    // El objeto error de GM_xmlhttpRequest es diferente a un error de fetch normal
                    reject(new Error(`Network error fetching ${url}: ${error.error || 'Unknown error'}`));
                }
            });
        });
    }

    // Función para añadir el botón flotante de descarga
    function addDownloadAllButton() {
        const downloadButton = document.createElement('button');
        downloadButton.textContent = 'Descargar Todos los Scripts';
        downloadButton.id = 'downloadAllGreasyforkScripts';

        // Estilos para posicionar el botón flotante en la parte inferior derecha
        downloadButton.style.position = 'fixed';
        downloadButton.style.bottom = '20px';
        downloadButton.style.right = '20px';
        downloadButton.style.zIndex = '99999';

        // Estilos visuales del botón
        downloadButton.style.padding = '12px 25px';
        downloadButton.style.backgroundColor = '#4CAF50';
        downloadButton.style.color = 'white';
        downloadButton.style.border = 'none';
        downloadButton.style.borderRadius = '8px';
        downloadButton.style.cursor = 'pointer';
        downloadButton.style.fontSize = '18px';
        downloadButton.style.fontWeight = 'bold';
        downloadButton.style.boxShadow = '0 4px 8px rgba(0,0,0,0.3)';
        downloadButton.style.transition = 'background-color 0.3s ease, transform 0.1s ease, box-shadow 0.3s ease';

        // Efectos visuales al pasar el ratón y al hacer click
        downloadButton.onmouseover = () => {
            if (!downloadButton.disabled) {
                downloadButton.style.backgroundColor = '#45a049';
                downloadButton.style.boxShadow = '0 6px 12px rgba(0,0,0,0.4)';
            }
        };
        downloadButton.onmouseout = () => {
            if (!downloadButton.disabled) {
                downloadButton.style.backgroundColor = '#4CAF50';
                downloadButton.style.boxShadow = '0 4px 8px rgba(0,0,0,0.3)';
            }
        };
        downloadButton.onmousedown = () => {
            if (!downloadButton.disabled) downloadButton.style.transform = 'translateY(2px)';
        };
        downloadButton.onmouseup = () => {
            if (!downloadButton.disabled) downloadButton.style.transform = 'translateY(0)';
        };

        downloadButton.addEventListener('click', downloadAllScripts);

        document.body.appendChild(downloadButton);
        console.log('[Greasyfork Downloader] Botón de descarga añadido al DOM.');
    }

    async function downloadAllScripts() {
        const scriptListItems = document.querySelectorAll('li[data-code-url]');

        console.log(`[Greasyfork Downloader] Se encontraron ${scriptListItems.length} scripts potenciales.`);

        if (scriptListItems.length === 0) {
            alert('No se encontraron scripts. Asegúrate de estar en la pestaña "Scripts" del perfil del usuario y que el usuario tenga scripts públicos. Consulta la consola (F12) para más detalles.');
            console.error('[Greasyfork Downloader] No se encontraron elementos <li> con data-code-url en el selector: li[data-code-url]');
            return;
        }

        const downloadQueue = [];
        scriptListItems.forEach((li, index) => {
            const url = li.getAttribute('data-code-url');
            if (!url) {
                console.warn(`[Greasyfork Downloader] Elemento <li> sin data-code-url en el índice ${index}:`, li);
                return;
            }

            // Saneamiento robusto del nombre del archivo
            let filenameSegment = url.substring(url.lastIndexOf('/') + 1).split('?')[0];
            let filename = decodeURIComponent(filenameSegment);

            // Eliminar caracteres que no sean alfanuméricos, guiones, guiones bajos o puntos
            // Y asegurar que el .user.js o .user.css final se mantenga
            let baseName = filename.replace(/\.(user\.js|user\.css)$/i, ''); // Separar la extensión
            let extension = (filename.match(/\.(user\.js|user\.css)$/i) || ['.user.js'])[0]; // Obtener la extensión o default .user.js

            baseName = baseName.replace(/[^A-Za-z0-9\-_.]/g, ''); // Limpiar el nombre base

            // Si el nombre base queda vacío o muy corto, usa el script ID o un UUID
            if (baseName.length < 3) {
                const scriptId = li.getAttribute('data-script-id') || `unknown_${index}`;
                baseName = `greasyfork_script_${scriptId}`;
            }

            // Recomponer el nombre del archivo
            const finalFilename = `${baseName}${extension}`;

            downloadQueue.push({ url, filename: finalFilename });
        });

        const totalScripts = downloadQueue.length;
        let downloadedCount = 0;

        alert(`Se encontraron ${totalScripts} scripts para descargar. Iniciando descarga de cada uno...`);

        const downloadButton = document.getElementById('downloadAllGreasyforkScripts');
        if (downloadButton) {
            downloadButton.disabled = true;
            downloadButton.textContent = `Descargando 0/${totalScripts}...`;
            downloadButton.style.backgroundColor = '#cccccc';
            downloadButton.style.cursor = 'not-allowed';
            downloadButton.style.boxShadow = 'none';
        }

        // Implementar el retardo entre descargas
        const delayMs = 1200; // 1.2 segundos entre cada descarga para evitar bloqueos

        for (const { url, filename } of downloadQueue) {
            console.log(`[Greasyfork Downloader] Intentando descargar: "${filename}" desde "${url}"`);
            try {
                await downloadFileRobustly({ url, filename });
                downloadedCount++;
                if (downloadButton) {
                    downloadButton.textContent = `Descargando ${downloadedCount}/${totalScripts}...`;
                }
                console.log(`[Greasyfork Downloader] Descargado con éxito: "${filename}"`);
            } catch (error) {
                console.error(`[Greasyfork Downloader] ERROR al descargar "${filename}":`, error);
                // Continuar con el siguiente script aunque este falle
            }
            // Retardo para evitar el bloqueo del navegador por descargas simultáneas
            if (downloadedCount < totalScripts) { // No esperar después de la última descarga
                await new Promise(resolve => setTimeout(resolve, delayMs));
            }
        }

        setTimeout(() => {
            alert(`Se completó el intento de descarga de ${downloadedCount} de ${totalScripts} scripts. Revise las descargas de su navegador y la consola (F12) para cualquier error.`);

            if (downloadButton) {
                downloadButton.disabled = false;
                downloadButton.textContent = 'Descargar Todos los Scripts';
                downloadButton.style.backgroundColor = '#4CAF50';
                downloadButton.style.cursor = 'pointer';
                downloadButton.style.boxShadow = '0 4px 8px rgba(0,0,0,0.3)';
            }
        }, 1000); // Pequeño retraso final antes de la alerta.
    }

    // Inicialización del script
    function initializeScript() {
        console.log('[Greasyfork Downloader] Inicializando script...');
        addDownloadAllButton();
    }

    if (document.readyState === 'loading') {
        window.addEventListener('DOMContentLoaded', () => setTimeout(initializeScript, 500));
    } else {
        setTimeout(initializeScript, 500);
    }

})();