您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Fügt "Alle herunterladen"-Buttons hinzu, um alle Ressourcen in einem Moodle-Kurs herunterzuladen.
// ==UserScript== // @name Moodle Resourcen Downloader // @namespace http://rococolabs.org // @description Fügt "Alle herunterladen"-Buttons hinzu, um alle Ressourcen in einem Moodle-Kurs herunterzuladen. // @version 2.1 // @run-at document-end // @match https://moodle.thi.de/course/view.php* // @grant none // @require https://cdnjs.cloudflare.com/ajax/libs/jszip/3.7.1/jszip.min.js // @require https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.5/FileSaver.min.js // @license MIT // ==/UserScript== (function() { 'use strict'; // Utility function to clean special characters from strings function cleanSpecialCharacters(string) { return string.replace(/[\u00E0-\u00FC]/g, (match) => { const specialChars = { 'á': 'a', 'é': 'e', 'í': 'i', 'ó': 'o', 'ú': 'u', 'ñ': 'n', 'ç': 'c', 'à': 'a', 'è': 'e', 'ì': 'i', 'ò': 'o', 'ù': 'u', 'ä': 'a', 'ë': 'e', 'ï': 'i', 'ö': 'o', 'ü': 'u' }; return specialChars[match] || ''; }).replace(/[^\w\s]/gi, '').replace(/\s+/g, '_'); } // Function to fetch the course name function fetchCourseName() { const courseTitleElement = document.querySelector('.page-header-headings h1'); return courseTitleElement ? cleanSpecialCharacters(courseTitleElement.innerText) : 'Course'; } // Function to create and show loading spinner and status message function showLoadingSpinner() { const overlay = document.createElement('div'); overlay.id = 'loading-overlay'; overlay.style.position = 'fixed'; overlay.style.top = '0'; overlay.style.left = '0'; overlay.style.width = '100%'; overlay.style.height = '100%'; overlay.style.backgroundColor = 'rgba(0, 0, 0, 0.7)'; overlay.style.display = 'flex'; overlay.style.flexDirection = 'column'; overlay.style.justifyContent = 'center'; overlay.style.alignItems = 'center'; overlay.style.zIndex = '10000'; document.body.appendChild(overlay); const spinner = document.createElement('div'); spinner.id = 'loading-spinner'; spinner.style.border = '16px solid rgba(0, 0, 0, 0)'; spinner.style.borderTop = '16px solid #00ffcc'; spinner.style.borderRadius = '50%'; spinner.style.width = '120px'; spinner.style.height = '120px'; spinner.style.animation = 'spin 2s linear infinite'; overlay.appendChild(spinner); const status = document.createElement('div'); status.id = 'status-message'; status.style.color = '#00ffcc'; status.style.fontFamily = 'monospace'; status.style.backgroundColor = 'transparent'; status.style.padding = '10px'; status.style.border = '2px solid #00ffcc'; status.style.borderRadius = '0'; status.style.textAlign = 'center'; status.style.marginTop = '20px'; overlay.appendChild(status); const style = document.createElement('style'); style.textContent = ` @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } `; document.head.appendChild(style); } // Function to update status message function updateStatusMessage(message) { const status = document.getElementById('status-message'); if (status) { status.textContent = message; } } // Function to hide loading spinner and status message function hideLoadingSpinner() { const overlay = document.getElementById('loading-overlay'); if (overlay) { overlay.remove(); } } // Function to get file extension from URL function getFileExtension(url) { const pathname = new URL(url).pathname; const basename = pathname.split('/').pop(); const match = basename.match(/\.([a-zA-Z0-9]+)$/); return match ? match[1] : ''; } // Function to download and zip all resources using JSZip async function downloadAllResources(group, zip) { const links = document.querySelectorAll(`#${group} .activity.resource a, #${group} .activity.folder a`); for (const link of links) { const href = link.getAttribute('href'); if (href && !href.includes('session-')) { try { updateStatusMessage(`Hole ${link.innerText.trim()}...`); const response = await fetch(href); const text = await response.text(); const doc = new DOMParser().parseFromString(text, 'text/html'); const downloadLink = doc.querySelector('.resourceworkaround a')?.href || href; const filenameBase = cleanSpecialCharacters(link.innerText.trim()) || 'download'; const fileExtension = getFileExtension(downloadLink); const filename = `${filenameBase}.${fileExtension}`; updateStatusMessage(`Lade ${filename} herunter...`); const fileResponse = await fetch(downloadLink); const fileBlob = await fileResponse.blob(); zip.file(filename, fileBlob, {binary: true}); // If it's a folder, recursively download its contents if (link.closest('.modtype_folder')) { await downloadFolderContents(href, filenameBase, zip); } } catch (error) { console.error(`Fehler beim Herunterladen der Ressource von ${href}:`, error); } } } } // Function to download contents of a folder async function downloadFolderContents(folderUrl, parentFolderName, zip) { try { const response = await fetch(folderUrl); const text = await response.text(); const doc = new DOMParser().parseFromString(text, 'text/html'); const folderLinks = doc.querySelectorAll('.fp-filename-icon a'); for (const link of folderLinks) { const href = link.getAttribute('href'); if (href && !href.includes('session-')) { const filenameBase = cleanSpecialCharacters(link.querySelector('.fp-filename').innerText.trim()) || 'download'; const fileExtension = getFileExtension(href); const filename = `${parentFolderName}/${filenameBase}.${fileExtension}`; updateStatusMessage(`Lade ${filename} herunter...`); const fileResponse = await fetch(href); const fileBlob = await fileResponse.blob(); zip.file(filename, fileBlob, {binary: true}); } } } catch (error) { console.error(`Fehler beim Herunterladen des Verzeichnisses von ${folderUrl}:`, error); } } // Function to download and zip all resources from the entire course using JSZip async function downloadAllCourseResources() { const zip = new JSZip(); const sections = document.querySelectorAll('.topics>li.section.main'); const courseName = fetchCourseName(); showLoadingSpinner(); updateStatusMessage('Download startet...'); for (const section of sections) { const group = section.getAttribute('id'); await downloadAllResources(group, zip); } updateStatusMessage('Erstelle ZIP...'); zip.generateAsync({ type: 'blob', compression: 'DEFLATE' }).then(function(content) { hideLoadingSpinner(); saveAs(content, `${courseName}.zip`); }).catch(function(error) { hideLoadingSpinner(); console.error('Fehler beim Erstellen der ZIP-Datei:', error); }); } // Button styling function createStyledButton(id, text) { const button = document.createElement('button'); button.id = id; button.textContent = text; button.style.backgroundColor = '#1d1d1d'; button.style.color = '#00ffcc'; button.style.border = '2px solid #00ffcc'; button.style.padding = '8px 20px'; button.style.textAlign = 'center'; button.style.textDecoration = 'none'; button.style.display = 'inline-block'; button.style.fontSize = '14px'; button.style.margin = '4px 2px'; button.style.cursor = 'pointer'; button.style.borderRadius = '0'; button.style.fontFamily = 'monospace'; button.style.boxShadow = '0 4px 8px 0 rgba(0,0,0,0.2), 0 6px 20px 0 rgba(0,0,0,0.19)'; button.addEventListener('mouseenter', () => { button.style.backgroundColor = '#00ffcc'; button.style.color = '#1d1d1d'; }); button.addEventListener('mouseleave', () => { button.style.backgroundColor = '#1d1d1d'; button.style.color = '#00ffcc'; }); return button; } // Inject buttons into the page function addDownloadButtons() { // Add course-wide download button const courseHeader = document.querySelector('.page-header-headings'); if (courseHeader) { const downloadAllCourseButton = createStyledButton('triggerall-course', 'Alle Kurs-Ressourcen herunterladen'); downloadAllCourseButton.addEventListener('click', (e) => { e.preventDefault(); downloadAllCourseResources(); }); const courseButtonContainer = document.createElement('div'); courseButtonContainer.style.display = 'flex'; courseButtonContainer.style.justifyContent = 'left'; courseButtonContainer.style.marginBottom = '20px'; courseButtonContainer.appendChild(downloadAllCourseButton); courseHeader.appendChild(courseButtonContainer); } // Add section-specific download buttons const sections = document.querySelectorAll('.topics>li.section.main'); const courseName = fetchCourseName(); sections.forEach(section => { const group = section.getAttribute('id'); const downloadAllButton = createStyledButton(`triggerall-${group}`, 'Alle Ressourcen herunterladen'); downloadAllButton.addEventListener('click', async (e) => { e.preventDefault(); const zip = new JSZip(); showLoadingSpinner(); updateStatusMessage('Download startet...'); await downloadAllResources(group, zip); updateStatusMessage('Erstelle ZIP...'); zip.generateAsync({ type: 'blob', compression: 'DEFLATE' }).then(function(content) { hideLoadingSpinner(); saveAs(content, `${courseName}_${group}.zip`); }).catch(function(error) { hideLoadingSpinner(); console.error('Fehler beim Erstellen der ZIP-Datei:', error); }); }); const downloadContainer = document.createElement('div'); downloadContainer.style.display = 'flex'; downloadContainer.style.justifyContent = 'flex-end'; downloadContainer.style.marginTop = '10px'; downloadContainer.appendChild(downloadAllButton); section.querySelector('.content').appendChild(downloadContainer); }); } addDownloadButtons(); })();