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