feat: 60 recursos de lectura descargados (44 PDFs, 10 ZIPs)
This commit is contained in:
@@ -0,0 +1,233 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Descarga los recursos (PDFs, archivos) de todas las secciones.
|
||||
* Usa navegación directa a cada URL de recurso para disparar la descarga.
|
||||
*/
|
||||
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
import { chromium } from "playwright";
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
const DOWNLOADS_DIR = path.join(__dirname, "descargas");
|
||||
const SESSION_FILE = path.join(__dirname, "session.json");
|
||||
|
||||
const CREDENTIALS = {
|
||||
username: "saldis_karina@hotmail.com",
|
||||
password: "Martes13#",
|
||||
};
|
||||
|
||||
const CAMPUS_BASE = "https://voley.onlineeducation.center/es/campus/student/training/e/14197";
|
||||
|
||||
const SECCIONES = [
|
||||
{ modulo: "Modulo_1_Planificacion", nombre: "Planificacion", id: "s/34868", moduloPath: "m/19096" },
|
||||
{ modulo: "Modulo_1_Planificacion", nombre: "Preparacion_Atletica", id: "s/34869", moduloPath: "m/19096" },
|
||||
{ modulo: "Modulo_1_Planificacion", nombre: "Explicacion_TP", id: "s/34870", moduloPath: "m/19096" },
|
||||
{ modulo: "Modulo_1_Planificacion", nombre: "Seguimientos_Equipos", id: "s/35523", moduloPath: "m/19096" },
|
||||
{ modulo: "Modulo_1_Planificacion", nombre: "Analisis_Equipos_1", id: "s/35524", moduloPath: "m/19096" },
|
||||
{ modulo: "Modulo_1_Planificacion", nombre: "Analisis_Equipos_2", id: "s/35525", moduloPath: "m/19096" },
|
||||
{ modulo: "Modulo_1_Planificacion", nombre: "TP_Final_Unidad", id: "s/35526", moduloPath: "m/19096" },
|
||||
{ modulo: "Modulo_2_Entrenamiento", nombre: "Centrales_y_Oponentes", id: "s/35519", moduloPath: "m/19097" },
|
||||
{ modulo: "Modulo_2_Entrenamiento", nombre: "Puntas_y_Liberos", id: "s/35520", moduloPath: "m/19097" },
|
||||
{ modulo: "Modulo_2_Entrenamiento", nombre: "Entrenamiento_Armadoras", id: "s/35521", moduloPath: "m/19097" },
|
||||
{ modulo: "Modulo_2_Entrenamiento", nombre: "Explicacion_TP", id: "s/35522", moduloPath: "m/19097" },
|
||||
];
|
||||
|
||||
function sleep(ms) { return new Promise(r => setTimeout(r, ms)); }
|
||||
|
||||
function classifyFile(filepath) {
|
||||
if (!fs.existsSync(filepath)) return filepath;
|
||||
const buffer = fs.readFileSync(filepath);
|
||||
let ext = null;
|
||||
|
||||
if (buffer[0] === 0x25 && buffer[1] === 0x50 && buffer[2] === 0x44 && buffer[3] === 0x46) {
|
||||
ext = ".pdf";
|
||||
} else if (buffer[0] === 0x50 && buffer[1] === 0x4B) {
|
||||
ext = ".zip"; // docx/xlsx/pptx son ZIP
|
||||
} else if (buffer.toString("utf8", 0, 200).includes("<!DOCTYPE") || buffer.toString("utf8", 0, 200).includes("<html")) {
|
||||
ext = ".html";
|
||||
}
|
||||
|
||||
if (ext && !filepath.endsWith(ext)) {
|
||||
const newPath = filepath.replace(/\.[^.]+$/, ext);
|
||||
fs.renameSync(filepath, newPath);
|
||||
return newPath;
|
||||
}
|
||||
return filepath;
|
||||
}
|
||||
|
||||
async function main() {
|
||||
console.log("📚 Descargando recursos de lectura de todas las secciones\n");
|
||||
|
||||
const browser = await chromium.launch({ headless: true, args: ["--no-sandbox"] });
|
||||
const context = await browser.newContext({
|
||||
viewport: { width: 1280, height: 900 },
|
||||
locale: "es-AR",
|
||||
acceptDownloads: true,
|
||||
});
|
||||
const page = await context.newPage();
|
||||
|
||||
// Login
|
||||
console.log("🔑 Login...");
|
||||
await page.goto(`${CAMPUS_BASE}/m/19096/s/34868/study-resources/list`, { waitUntil: "networkidle", timeout: 30000 });
|
||||
|
||||
if (!page.url().includes("/campus/")) {
|
||||
await page.waitForSelector("#login-email", { timeout: 10000 });
|
||||
await page.fill("#login-email", CREDENTIALS.username);
|
||||
await page.fill("#login-password", CREDENTIALS.password);
|
||||
await page.click("#btn-login");
|
||||
await page.waitForURL("**/campus/**", { timeout: 30000 });
|
||||
await context.storageState({ path: SESSION_FILE });
|
||||
}
|
||||
console.log(" ✅ Autenticado\n");
|
||||
|
||||
let totalDescargados = 0;
|
||||
let totalErrores = 0;
|
||||
|
||||
for (const sec of SECCIONES) {
|
||||
const url = `${CAMPUS_BASE}/${sec.moduloPath}/${sec.id}/study-resources/list`;
|
||||
const dirLocal = path.join(DOWNLOADS_DIR, "secciones", sec.modulo, sec.nombre);
|
||||
fs.mkdirSync(dirLocal, { recursive: true });
|
||||
|
||||
console.log(`📂 ${sec.nombre}`);
|
||||
|
||||
try {
|
||||
await page.goto(url, { waitUntil: "networkidle", timeout: 30000 });
|
||||
await sleep(2000);
|
||||
|
||||
// Guardar HTML actualizado
|
||||
const html = await page.content();
|
||||
if (!html.includes("login-email")) {
|
||||
fs.writeFileSync(path.join(dirLocal, "pagina.html"), html);
|
||||
}
|
||||
|
||||
// Extraer todos los enlaces de recursos
|
||||
const recursos = await page.evaluate(() => {
|
||||
const items = [];
|
||||
document.querySelectorAll("a[href]").forEach(el => {
|
||||
const href = el.getAttribute("href"); // relative
|
||||
const fullHref = el.href; // absolute
|
||||
if (fullHref.includes("/study-resources/") && fullHref.includes("/show")) {
|
||||
items.push({
|
||||
tipo: "archivo-curso",
|
||||
href: href,
|
||||
url: fullHref,
|
||||
texto: el.textContent.trim(),
|
||||
id: fullHref.match(/study-resources\/(\d+)\/show/)?.[1] || "",
|
||||
});
|
||||
}
|
||||
});
|
||||
document.querySelectorAll("iframe[src]").forEach(el => {
|
||||
const src = el.src;
|
||||
if (src.includes("vimeo.com") || src.includes("youtube.com")) {
|
||||
items.push({ tipo: "video-embed", url: src, id: src.match(/vimeo\.com\/video\/(\d+)/)?.[1] || "" });
|
||||
}
|
||||
});
|
||||
return items;
|
||||
});
|
||||
|
||||
fs.writeFileSync(path.join(dirLocal, "recursos.json"), JSON.stringify(recursos, null, 2));
|
||||
|
||||
// Filtrar solo "Abrir Archivo" (descargas reales, no enlaces a páginas)
|
||||
const archivos = recursos.filter(r =>
|
||||
r.tipo === "archivo-curso" && r.texto.toLowerCase().includes("abrir archivo")
|
||||
);
|
||||
|
||||
if (archivos.length === 0) {
|
||||
console.log(` Sin archivos descargables\n`);
|
||||
continue;
|
||||
}
|
||||
|
||||
console.log(` 📥 ${archivos.length} archivos`);
|
||||
|
||||
for (const archivo of archivos) {
|
||||
const filename = `recurso_${archivo.id}.bin`;
|
||||
const filepath = path.join(dirLocal, filename);
|
||||
|
||||
// Si ya existe en cualquier extensión, saltar
|
||||
const existentes = fs.readdirSync(dirLocal).filter(f => f.startsWith(`recurso_${archivo.id}.`));
|
||||
if (existentes.length > 0) {
|
||||
console.log(` ↺ Ya existe: ${existentes[0]}`);
|
||||
totalDescargados++;
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
// Buscar el enlace por href parcial
|
||||
const linkSelector = `a[href*="study-resources/${archivo.id}/show"]`;
|
||||
const link = page.locator(linkSelector);
|
||||
const hasLink = await link.count() > 0;
|
||||
|
||||
// Intentar click + esperar download, con timeout corto
|
||||
let downloaded = false;
|
||||
if (hasLink) {
|
||||
try {
|
||||
const [download] = await Promise.all([
|
||||
page.waitForEvent("download", { timeout: 8000 }),
|
||||
link.first().click(),
|
||||
]);
|
||||
await download.saveAs(filepath);
|
||||
downloaded = true;
|
||||
} catch {
|
||||
// No fue un download, probablemente abrió una página
|
||||
}
|
||||
}
|
||||
|
||||
if (downloaded) {
|
||||
const finalPath = classifyFile(filepath);
|
||||
const sizeKB = (fs.statSync(finalPath).size / 1024).toFixed(1);
|
||||
console.log(` ✅ recurso_${archivo.id}${path.extname(finalPath)} (${sizeKB} KB)`);
|
||||
totalDescargados++;
|
||||
} else {
|
||||
// Navegar directamente a la URL del recurso
|
||||
const response = await page.goto(archivo.url, { waitUntil: "load", timeout: 15000 });
|
||||
const contentType = response?.headers()?.["content-type"] || "";
|
||||
|
||||
if (contentType.includes("pdf") || contentType.includes("octet-stream")) {
|
||||
const body = await response.body();
|
||||
fs.writeFileSync(filepath, body);
|
||||
const finalPath = classifyFile(filepath);
|
||||
const sizeKB = (fs.statSync(finalPath).size / 1024).toFixed(1);
|
||||
console.log(` ✅ recurso_${archivo.id}${path.extname(finalPath)} (${sizeKB} KB) [fetch]`);
|
||||
totalDescargados++;
|
||||
} else {
|
||||
// Es una página HTML — guardar contenido
|
||||
const htmlContent = await page.content();
|
||||
const htmlPath = path.join(dirLocal, `recurso_${archivo.id}.html`);
|
||||
fs.writeFileSync(htmlPath, htmlContent);
|
||||
const sizeKB = (fs.statSync(htmlPath).size / 1024).toFixed(1);
|
||||
console.log(` 📄 recurso_${archivo.id}.html (${sizeKB} KB) [web]`);
|
||||
totalDescargados++;
|
||||
}
|
||||
|
||||
// Volver a la sección para continuar
|
||||
await page.goto(url, { waitUntil: "networkidle", timeout: 30000 });
|
||||
await sleep(1000);
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
console.log(` ❌ ${archivo.id}: ${err.message.substring(0, 80)}`);
|
||||
totalErrores++;
|
||||
try {
|
||||
await page.goto(url, { waitUntil: "networkidle", timeout: 30000 });
|
||||
await sleep(1000);
|
||||
} catch {}
|
||||
}
|
||||
}
|
||||
console.log("");
|
||||
|
||||
} catch (err) {
|
||||
console.log(` ❌ Error sección: ${err.message}\n`);
|
||||
totalErrores++;
|
||||
}
|
||||
}
|
||||
|
||||
await browser.close();
|
||||
console.log(`\n📊 Resultados:`);
|
||||
console.log(` Descargados: ${totalDescargados}`);
|
||||
console.log(` Errores: ${totalErrores}`);
|
||||
console.log("✅ Listo. Ejecutá: node generar_dashboard.mjs");
|
||||
}
|
||||
|
||||
main();
|
||||
Reference in New Issue
Block a user