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.

От 06.08.2025. Виж последната версия.

// ==UserScript==
// @name         Descargador de Scripts de Usuario Greasyfork
// @namespace    http://tampermonkey.net/
// @version      1.2
// @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://greasyforks.org/es/users/*
// @match        https://greasyforks.org/*/users/*
// @match        https://greasyforks.org/es/scripts/by-site/drawaria.online*
// @match        https://greasyforks.org/es/scripts/by-site/*
// @match        https://greasyforks.org/es/scripts*
// @match        https://greasyforks.org/*/scripts/by-site/drawaria.online*
// @match        https://greasyforks.org/*/scripts/by-site/*
// @match        https://greasyforks.org/*/scripts*
// @grant        GM_addStyle
// @grant        GM_xmlhttpRequest
// @connect      greasyforks.org
// @connect      update.greasyforks.org
// @license      MIT
// @icon         https://drawaria.online/avatar/cache/86e33830-86ea-11ec-8553-bff27824cf71.jpg
// ==/UserScript==

(function() {
    'use strict';
 // 1. LOCALIZA LA LISTA Y LA BARRA DE OPCIONES
    function getList() {
        return document.querySelector('#browse-script-list');
    }

    function getOptionBar() {
        // Es la ul de la barra de ordenado
        return document.querySelector('#script-list-sort ul');
    }

    // 2. CREA EL BOTÓN DE ORDEN ANCIENT
    function addOldestButton() {
        const bar = getOptionBar();
        if (!bar || bar.querySelector('.sort-oldest-added')) return;
        const li = document.createElement('li');
        li.className = 'list-option sort-oldest-added';

        const a = document.createElement('a');
        a.href = "#";
        a.textContent = "Más antiguo";
        a.style.fontWeight = "bold";
        a.onclick = function(e){
            e.preventDefault();
            sortByOldest();
        };
        li.appendChild(a);
        bar.appendChild(li);
    }

    // 3. ACCIÓN DE ORDENAMIENTO
    function sortByOldest() {
        const list = getList();
        if (!list) return;
        const items = Array.from(list.querySelectorAll('li[data-script-created-date]'));
        // Ordenar por fecha ascendente (más antiguo primero)
        items.sort(function (a, b) {
            // Usa el valor data-script-created-date, que es YYYY-MM-DD (ISO)
            const da = new Date(a.getAttribute('data-script-created-date'));
            const db = new Date(b.getAttribute('data-script-created-date'));
            return da - db;
        });
        // Eliminar todos los items existentes
        while (list.firstChild) list.removeChild(list.firstChild);
        // Insertar en orden nuevo
        for (const it of items) list.appendChild(it);
    }

    // 4. ESPERA A QUE EL DOM ESTÉ LISTO Y EJECUTA
    function init() {
        addOldestButton();
    }
    // Por si tardan en cargar las opciones
    document.addEventListener('DOMContentLoaded', init);
    setTimeout(init, 1500);

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



})();
/*
    // 1. LOCALIZA LA LISTA Y LA BARRA DE OPCIONES
    function getList() {
        return document.querySelector('#browse-script-list');
    }

    function getOptionBar() {
        // Es la ul de la barra de ordenado
        return document.querySelector('#script-list-sort ul');
    }

    // 2. CREA EL BOTÓN DE ORDEN ANCIENT
    function addOldestButton() {
        const bar = getOptionBar();
        if (!bar || bar.querySelector('.sort-oldest-added')) return;
        const li = document.createElement('li');
        li.className = 'list-option sort-oldest-added';

        const a = document.createElement('a');
        a.href = "#";
        a.textContent = "Más antiguo";
        a.style.fontWeight = "bold";
        a.onclick = function(e){
            e.preventDefault();
            sortByOldest();
        };
        li.appendChild(a);
        bar.appendChild(li);
    }

    // 3. ACCIÓN DE ORDENAMIENTO
    function sortByOldest() {
        const list = getList();
        if (!list) return;
        const items = Array.from(list.querySelectorAll('li[data-script-created-date]'));
        // Ordenar por fecha ascendente (más antiguo primero)
        items.sort(function (a, b) {
            // Usa el valor data-script-created-date, que es YYYY-MM-DD (ISO)
            const da = new Date(a.getAttribute('data-script-created-date'));
            const db = new Date(b.getAttribute('data-script-created-date'));
            return da - db;
        });
        // Eliminar todos los items existentes
        while (list.firstChild) list.removeChild(list.firstChild);
        // Insertar en orden nuevo
        for (const it of items) list.appendChild(it);
    }

    // 4. ESPERA A QUE EL DOM ESTÉ LISTO Y EJECUTA
    function init() {
        addOldestButton();
    }
    // Por si tardan en cargar las opciones
    document.addEventListener('DOMContentLoaded', init);
    setTimeout(init, 1500);
*/
长期地址
遇到问题?请前往 GitHub 提 Issues。