234 lines
9.1 KiB
JavaScript
234 lines
9.1 KiB
JavaScript
#!/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();
|