Last active
January 16, 2024 14:58
-
-
Save steedalot/2744345d71d50e0ff3d684b9b6f3c856 to your computer and use it in GitHub Desktop.
Ermöglicht das Herunterladen einer kompletten Ordnerstruktur im alten Dateibereich der Bildungscloud
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==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