您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
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.
当前为
// ==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); */