Skip to content

Instantly share code, notes, and snippets.

@steedalot
Last active January 16, 2024 14:58
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save steedalot/2744345d71d50e0ff3d684b9b6f3c856 to your computer and use it in GitHub Desktop.
Save steedalot/2744345d71d50e0ff3d684b9b6f3c856 to your computer and use it in GitHub Desktop.
Ermöglicht das Herunterladen einer kompletten Ordnerstruktur im alten Dateibereich der Bildungscloud
// ==UserScript==
// @name Bildungscloud Ordner Download
// @version 0.6
// @description Herunterladen einner kompletten Ordnerstruktur im Dateibereich der Bildungscloud
// @author Daniel Gaida, N-21
// @match https://niedersachsen.cloud/files/*
// @match https://schulcloud-thueringen.de/files/*
// @match https://brandenburg.cloud/files/*
// @grant GM_xmlhttpRequest
// @grant unsafeWindow
// @connect s3.hidrive.strato.com
// @require https://cdnjs.cloudflare.com/ajax/libs/jszip/3.1.5/jszip.js
// ==/UserScript==
(function() {
'use strict';
const filesAndFoldersObject = {
folders: [],
files: []
};
let pendingRequests = 0;
let fileCounter = 0;
let downloadSize = 0;
let downloadCounter = 0;
let globalProgressElement = null;
let zipFileName = "Bildungscloud Download.zip";
//folder logic
// Generate complete URL for given folder id
function generateUrlFromFolderID(htmlDOM, folderId) {
const activeFolderTypeElement = htmlDOM.querySelector('ul.subitems li a.subitem.active');
const activeFolderTypePath = activeFolderTypeElement.getAttribute('href');
const activeFolderType = activeFolderTypePath.split('/')[2];
const sectionElement = htmlDOM.querySelector('section.section-upload');
let activeFolderOwner = sectionElement ? sectionElement.getAttribute('data-owner') : '';
if (activeFolderOwner != '') {
activeFolderOwner = activeFolderOwner + "/";
}
const completeFolderUrl = `/files/${activeFolderType}/${activeFolderOwner}${folderId}`;
console.log("The path being asked for is: " + completeFolderUrl);
return completeFolderUrl;
}
// Download the HTML for a folder element
function downloadFolderHTML(completeUrl, currentNode) {
//console.log("I am downloading folder " + completeUrl);
pendingRequests++;
GM_xmlhttpRequest({
method: "GET",
url: completeUrl,
onload: function(response) {
//console.log("The download function for " + completeUrl + " was called.");
const fetchedHTML = response.responseText;
parseHTMLForFoldersAndFiles(fetchedHTML, currentNode);
pendingRequests--;
checkIfFiletreeIsBuilt();
}
});
}
// Parse HTML and find file and folder elements
function parseHTMLForFoldersAndFiles(htmlDocument, currentNode) {
const parser = new DOMParser();
const htmlDOM = parser.parseFromString(htmlDocument, 'text/html');
// Parse folders
const folders = htmlDOM.querySelectorAll('a[data-method="dir-rename"][data-testid="edit-folder-name"]');
folders.forEach(function(folder) {
const folderId = folder.getAttribute('data-directory-id');
const folderName = folder.getAttribute('data-directory-name');
updateProgressElement("Ordner " + folderName + " wird durchsucht.");
const newFolderNode = {
folderId,
folderName,
files: [],
folders: []
};
currentNode.folders.push(newFolderNode);
const completeFolderUrl = generateUrlFromFolderID(htmlDOM, folderId);
downloadFolderHTML(completeFolderUrl, newFolderNode);
});
// Parse files
const files = htmlDOM.querySelectorAll('.files .row .col-sm-12[data-testid="files-section"] .card.file');
files.forEach(function(file) {
const fileId = file.getAttribute('data-file-id');
const fileName = file.getAttribute('data-file-name');
const fileSize = parseInt(file.getAttribute('data-file-size'));
const downloadLink = '/files/file?download=true&file=' + fileId + '&name=' + fileName;
currentNode.files.push({
fileId,
fileName,
fileSize,
downloadLink
});
fileCounter++;
downloadSize = downloadSize + fileSize;
});
}
// Check if the filetree object is fully built
function checkIfFiletreeIsBuilt() {
if (pendingRequests === 0) {
updateProgressElement(fileCounter + " Dateien gefunden.");
console.log(filesAndFoldersObject);
console.log("Total number of files: " + fileCounter);
if (downloadSize < 10000) {
downloadSize = downloadSize + " Byte";
}
else if (downloadSize < 10000000) {
downloadSize = Math.round(downloadSize/1024) + "KB";
}
else {
downloadSize = Math.round(downloadSize/1048576) + "MB";
}
let userResponse = confirm("Anzahl der gefundenen Dateien: " + fileCounter + "\nGesamtgröße des Downloads: " + downloadSize + "\n\nDateien herunterladen?");
if (userResponse == true) {
console.log("Download :)");
processAndDownload(filesAndFoldersObject)
.then(zipBlob => {
processZip(zipBlob);
resetEverything();
})
.catch(error => {
console.error("Error processing and downloading: ", error);
});
} else {
console.log("Abort :(");
resetEverything();
}
fileCounter = 0;
downloadSize = 0;
// Insert code to work with filesAndFoldersObject here
}
}
function downloadFile(downloadLink, fileName, fileSize) {
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: 'GET',
url: downloadLink,
responseType: 'blob',
onprogress: function(event) {
let percentComplete = "";
if (event.lengthComputable) {
percentComplete = " (" + Math.round((event.loaded / event.total) * 100) + "%)";
}
updateProgressElement("Datei " + downloadCounter + " (" + fileName + " | " + fileSize + ") wird heruntergeladen " + percentComplete + ".");
},
onload: function(response) {
downloadCounter++;
resolve({
data: response.response
});
},
onerror: function(err) {
console.warn(`Error downloading ${fileName}. Skipping.`);
resolve(null); // Resolve with null on error, signaling to skip this file.
}
});
});
}
function processZip(zipBlob) {
var blob = new Blob([zipBlob], { type: 'application/zip' });
var url = window.URL.createObjectURL(blob);
var a = document.createElement('a');
a.href = url;
a.download = zipFileName;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
}
async function processFolder(folder, parentZipFolder) {
for (const file of folder.files) {
let fileSize = "";
if (file.fileSize < 10000) {
fileSize = file.fileSize + " Byte";
}
else if (file.fileSize < 10000000) {
fileSize = Math.round(file.fileSize/1024) + "KB";
}
else {
fileSize = Math.round(file.fileSize/1048576) + "MB";
}
const fileData = await downloadFile(file.downloadLink, file.fileName, fileSize);
if (fileData) { // Only add if not null.
parentZipFolder.file(file.fileName, fileData.data);
}
}
for (const subfolder of folder.folders) {
const zipSubFolder = parentZipFolder.folder(subfolder.folderName);
await processFolder(subfolder, zipSubFolder);
}
}
async function processAndDownload(obj) {
const zip = new JSZip();
await processFolder(obj, zip);
updateProgressElement("Die Zip-Datei wird erstellt.");
// Generate and return the ZIP
const zipBlob = await zip.generateAsync({ type: "blob" });
return zipBlob;
}
function updateProgressElement(text) {
console.log("Updating the progress element.");
globalProgressElement.textContent = text;
}
function resetProgressElement() {
if (globalProgressElement) {
globalProgressElement.remove();
globalProgressElement = null;
console.log("Reset the progress element.");
}
}
function resetFilesAndFoldersObject() {
filesAndFoldersObject.folders = [];
filesAndFoldersObject.files = [];
}
function resetEverything() {
resetProgressElement();
resetFilesAndFoldersObject();
pendingRequests = 0;
fileCounter = 0;
downloadSize = 0;
downloadCounter = 0;
zipFileName = "NBC Download.zip";
}
// Locate all folder elements
const folderElements = document.querySelectorAll('.folder');
// Create a download folder button element and anchor
const downloadFolderButtonAnchor = document.createElement('a');
downloadFolderButtonAnchor.setAttribute("title", "Ordnerinhalte als Zip-Datei herunterladen");
const downloadFolderButton = document.createElement('i');
downloadFolderButton.classList.add('fa', 'fa-cloud-download', 'directory-icon');
downloadFolderButtonAnchor.appendChild(downloadFolderButton);
// Loop through each folder element and insert the download folder button
folderElements.forEach(folderElement => {
const anchorClone = downloadFolderButtonAnchor.cloneNode(true); // Clone the anchor element
anchorClone.addEventListener('click', function(event) {
event.stopPropagation();
const folderElement = event.target.closest('.folder');
const folderId = folderElement.getAttribute('data-folder-id');
const completeUrl = generateUrlFromFolderID(document, folderId);
// get the folder name for the zip file
const nextAElement = event.target.parentElement.nextElementSibling;
if (nextAElement && nextAElement.hasAttribute('data-directory-name')) {
zipFileName = nextAElement.getAttribute('data-directory-name') + ".zip";
}
// Create the <strong> element for the download progress
const progressElement = document.createElement('strong');
progressElement.classList.add('card-title-directory');
progressElement.textContent = "Daten werden gesammelt."; // initial value
globalProgressElement = progressElement;
// Position the <strong> element
pullRightDiv.insertBefore(progressElement, anchorClone);
downloadFolderHTML(completeUrl, filesAndFoldersObject);
});
const pullRightDiv = folderElement.querySelector('.pull-right');
pullRightDiv.insertBefore(anchorClone, pullRightDiv.firstChild);
});
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment