PolitoDownloader

Download all your Polito material in one click

Tendrás que instalar una extensión para tu navegador como Tampermonkey, Greasemonkey o Violentmonkey si quieres utilizar este script.

You will need to install an extension such as Tampermonkey to install this script.

Tendrás que instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Userscripts para instalar este script.

Tendrás que instalar una extensión como Tampermonkey antes de poder instalar este script.

Necesitarás instalar una extensión para administrar scripts de usuario si quieres instalar este script.

(Ya tengo un administrador de scripts de usuario, déjame instalarlo)

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

(Ya tengo un administrador de estilos de usuario, déjame instalarlo)

// ==UserScript==
// @name         PolitoDownloader
// @namespace    https://github.com/giuseppe-dandrea
// @version      0.15
// @description  Download all your Polito material in one click
// @author       giuseppe-dandrea
// @supportURL   https://github.com/giuseppe-dandrea/PolitoDownloader/issues
// @match        https://didattica.polito.it/pls/portal30/sviluppo.pagina_corso.main*
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_xmlhttpRequest
// @require      https://cdnjs.cloudflare.com/ajax/libs/jszip/3.1.5/jszip.min.js
// ==/UserScript==

(function() {
	"use strict";

	const URL = "https://didattica.polito.it/pls/portal30/sviluppo.filemgr.handler";

	// List the content of a directory
	// callback(pathList, parentPath, parentZipFolder, downloadAll)
	// pathList contains the objects of the files and dirs
	function listPath(path, code, callback, parentZipFolder, downloadAll) {
		N_FILE++;
		if (path === "/") {
			code = "";
		}
		let params = "?action=list&path=" + encodeURIComponent(path) + "&code=" + code;
		let xhttp = new XMLHttpRequest();

		activeDownloadButton.innerText = "Retrieving files...";

		xhttp.open("POST", URL + params);
		xhttp.send();
		xhttp.onreadystatechange = function() {
			if (xhttp.readyState == 4 && xhttp.status == 200) {
				let pathList = JSON.parse(xhttp.responseText);
				pathList = pathList.result.filter(o => !o.name.includes("ZZZZZZZZZZZZZZZZZZZZLezioni on-line"));
				N_FILE--;
				if (pathList.length === 0) {
					return;
				}
				if (callback) callback(pathList, path, parentZipFolder, downloadAll);
			}
		}
	}

	// callback for listPath:
	// for every object in path list:
	//   if the object is a dir: create the dir in the zip and call listFiles in that dir
	//   else download and add the file to the zip
	function listPathHandler(pathList, parentPath, parentFolder, downloadAll) {
		pathList.forEach(o => {
			if (o.type == "dir") {
				// console.log("Created dir " + o.name);
				let newFolder = parentFolder.folder(o.name);
				listPath(parentPath + o.name + "/", o.code, listPathHandler, newFolder, downloadAll);
			} else if (o.type == "file" && (downloadAll || (DOWNLOADED_FILES[o.code] ? o.date > DOWNLOADED_FILES[o.code] : true))) {
				N_FILE++;
				// console.log("Added " + o.name);
				DOWNLOADED_FILES[o.code] = o.date;
				let params = "?action=download&path=" + encodeURIComponent(parentPath + o.name) + "&code=" + o.code;
				let xhttp = new XMLHttpRequest();
				xhttp.open("POST", URL + params);
				xhttp.responseType = "blob";
				xhttp.send();
				xhttp.onreadystatechange = function() {
					if (xhttp.readyState == 4 && xhttp.status == 200) {
						parentFolder.file(o.name, xhttp.response, { binary: true });
						// console.log("1 file added!");
						N_DOWNLOADED++;
						updateProgressBar(( (N_DOWNLOADED + N_PREVIOUS_DOWNLOADED) / TOTAL_FILES ) * 100);
						N_FILE--;
					}
				}
			} else {
				N_PREVIOUS_DOWNLOADED++;
				updateProgressBar(( (N_DOWNLOADED + N_PREVIOUS_DOWNLOADED) / TOTAL_FILES ) * 100);

			}

		});
	}

	function saveFile(blob, name) {
		let a = document.createElement("a");
		document.body.appendChild(a);
		a.style = "display: none";
		let url = window.URL.createObjectURL(blob);
		a.href = url;
		a.download = name;
		a.click();
		window.URL.revokeObjectURL(url);
		a.parentNode.removeChild(a);
	}

	function downloadZip(zip, name) {
		// console.log("Inizio a comprimere!");
		zip.generateAsync({ type:"blob" }).then(function(content) {
			saveFile(content, name);
			activeDownloadButton.innerText = activeButtonText;
		});
	}

	function onCompleted(callback) {
		setTimeout(function() {
			// console.log(N_FILE);
			if (N_FILE === 0) {
				activeDownloadButton.innerText = "Downloading...";
				callback();
			} else {
				onCompleted(callback);
			}
		}, 1000);
	}

	function initGlobals(button) {
		zip = new JSZip();
		N_FILE = 0;
		N_DOWNLOADED = 0;
		N_PREVIOUS_DOWNLOADED = 0;
		DOWNLOADED_FILES = GM_getValue("downloadedFiles", {});
		activeDownloadButton = button;
		activeButtonText = button.innerText;
	}

	function onButtonClick(button, downloadAll, failText) {
		initGlobals(button);
		progressBar.parentNode.style.display = "block"
		if (TOTAL_FILES == 0) {
			activeDownloadButton.innerText = "No files!";
			return
		}
		listPath("/", 0, listPathHandler, zip, downloadAll);
		// console.log("File download completed\nStarting onCompleted");
		onCompleted(function() {
			GM_setValue("downloadedFiles", DOWNLOADED_FILES);
			if (N_DOWNLOADED > 0) {
				downloadZip(zip, title);
			} else {
				activeDownloadButton.innerText = failText;
			}
			badge.style.display = "none";
			progressBar.parentNode.style.display = "none"
			updateProgressBar(0);
			GMlastUpdate[code] = lastUpdate;
			GM_setValue("lastUpdate", GMlastUpdate);
		});
	}

	function updateProgressBar(percent) {
		percent = Math.round(percent);
		progressBar.style.width = percent + "%";
		progressBar.setAttribute("aria-valuenow", percent);
	}

	// download all
	let downloadAllButton = document.createElement("button");
	downloadAllButton.innerText = "Download All Files";
	downloadAllButton.setAttribute("id", "downloadAllButton");
	downloadAllButton.setAttribute("class", "btn btn-primary");

	// download new
	let downloadNewButton = document.createElement("button");
	downloadNewButton.innerText = "Download New Files";
	downloadNewButton.setAttribute("id", "downloadNewButton");
	downloadNewButton.setAttribute("class", "btn btn-primary");
	downloadNewButton.style["margin-left"] = "5px";

	// new badge
	let badge = document.createElement("div");
	badge.style.cssText = `
		background: red;
		width: 10px;
		height: 10px;
		border-radius: 50%;
		position: relative;
		z-index: 1000;
		top: -30px;
		left: 142px;
		margin-bottom: -10px;
		display: none;`
	downloadNewButton.append(badge);

	// progress bar
	let progressBarStr = 	'<div class="progress">\
							<div class="progress-bar" id="progressbar" role="progressbar"\
							  aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" style="width:0%">\
							</div>\
						</div>'
	let progressBar = document.createElement("div");
	progressBar.innerHTML = progressBarStr;
	progressBar = progressBar.firstChild.children[0];
	progressBar.parentNode.style.display = "none";
	progressBar.parentNode.style.margin = "10px";

	// page title
	let title = document.querySelector("body > div:nth-child(9) > div > div > h2 > strong").innerText;
	let code = title.match(/\w+/)[0];
	let TOTAL_FILES = 0;

	let lastUpdate = 0;
	let GMlastUpdate = GM_getValue("lastUpdate", {});

	// Code of the root directory, used to obtain TOTAL_FILES and lastUpdate
	let rootCode = document.documentElement.innerHTML.match(/rootCode: "(\d+)/)[1];
	if (rootCode) {
		GM_xmlhttpRequest({
			method: "POST",
			url:    "https://didattica.polito.it/pls/portal30/sviluppo.filemgr.get_process_amount?items=" + rootCode,
			onload: function(resp) {
				let result = JSON.parse(resp.response).result;
				TOTAL_FILES = result.files;
				lastUpdate = Date.parse(result.lastUpload);
				if (!GMlastUpdate[code] || GMlastUpdate[code] < lastUpdate) {
					badge.style.display = "block";
				}
			}
		});
	}

	// center tag
	let centerTag = document.createElement("center");
	centerTag.appendChild(downloadAllButton);
	centerTag.appendChild(downloadNewButton);
	document.querySelector("#portlet_corso_container > div > div > div.row.text-left > div > div:nth-child(2)").prepend(centerTag);
	centerTag.parentNode.insertBefore(progressBar.parentNode, centerTag.nextSibling)

	// global vars
	let zip;
	let N_FILE;
	let N_DOWNLOADED;
	let N_PREVIOUS_DOWNLOADED;
	let DOWNLOADED_FILES;
	let activeDownloadButton;
	let activeButtonText;

	// download all listener
	document.getElementById("downloadAllButton").onclick = function() {
		onButtonClick(downloadAllButton, true, "No files!");
	}

	// download new listener
	document.getElementById("downloadNewButton").onclick = function() {
		onButtonClick(downloadNewButton, false, "No new files!");
	}
})();