WebUntis Topic Scraper

Scrapt Lehrstoff vollautomatisch von den einzelnen Unterrichtsstunden

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

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

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

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

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

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

Advertisement:

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

Advertisement:

// ==UserScript==
// @name         WebUntis Topic Scraper
// @namespace    http://tampermonkey.net/
// @namespace    https://github.com/dein-username
// @license      MIT
// @homepageURL  https://greasyfork.org/
// @version      0.1.0
// @description  Scrapt Lehrstoff vollautomatisch von den einzelnen Unterrichtsstunden
// @author       Whiztech
// @match        https://gds2.webuntis.com/timetable/my-student*
// @grant        GM_setValue
// @grant        GM_getValue
// ==/UserScript==

(function () {
    'use strict';

    function icon(paths, size) {
        size = size || 15;
        var ns = 'http://www.w3.org/2000/svg';
        var svg = document.createElementNS(ns, 'svg');
        svg.setAttribute('width',  size);
        svg.setAttribute('height', size);
        svg.setAttribute('viewBox', '0 0 24 24');
        svg.setAttribute('fill', 'none');
        svg.setAttribute('stroke', 'currentColor');
        svg.setAttribute('stroke-width', '2');
        svg.setAttribute('stroke-linecap', 'round');
        svg.setAttribute('stroke-linejoin', 'round');
        svg.style.cssText = 'display:inline-block;vertical-align:middle;flex-shrink:0;';
        paths.forEach(function(d) {
            var p = document.createElementNS(ns, 'path');
            p.setAttribute('d', d);
            svg.appendChild(p);
        });
        return svg;
    }

    var ICONS = {
        play:      ['M5 3l14 9-14 9V3z'],
        copy:      ['M8 4H6a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2h-2','M16 2H8a2 2 0 0 0-2 2v0a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2v0a2 2 0 0 0-2-2z'],
        eye:       ['M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z','M12 9a3 3 0 1 0 0 6 3 3 0 0 0 0-6z'],
        trash:     ['M3 6h18','M19 6l-1 14H6L5 6','M8 6V4h8v2','M10 11v6','M14 11v6'],
        check:     ['M20 6L9 17l-5-5'],
        xmark:     ['M18 6 6 18','M6 6l12 12'],
        clipboard: ['M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2','M9 2h6a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1H9a1 1 0 0 1-1-1V3a1 1 0 0 1 1-1z'],
        loader:    ['M12 2v4','M12 18v4','M4.93 4.93l2.83 2.83','M16.24 16.24l2.83 2.83','M2 12h4','M18 12h4','M4.93 19.07l2.83-2.83','M16.24 7.76l2.83-2.83'],
    };

    function makeBtn(labelText, iconName, bg, bgHover) {
        var btn = document.createElement('button');
        btn.style.cssText = [
            'width:100%;padding:8px 10px;background:' + bg + ';color:#fff;',
            'border:none;border-radius:8px;font-size:12.5px;font-weight:600;cursor:pointer;',
            'transition:background .15s;margin-bottom:6px;',
            'display:flex;align-items:center;gap:7px;',
        ].join('');
        btn.appendChild(icon(ICONS[iconName], 14));
        var span = document.createElement('span');
        span.textContent = labelText;
        btn.appendChild(span);
        btn.addEventListener('mouseenter', function() { btn.style.background = bgHover; });
        btn.addEventListener('mouseleave', function() { btn.style.background = bg; });
        btn._span = span;
        return btn;
    }

    function sep() {
        var hr = document.createElement('hr');
        hr.style.cssText = 'border:none;border-top:1px solid rgba(0,0,0,0.08);margin:10px 0;';
        return hr;
    }

    function showModal(title, message, buttons) {
        var overlay = document.createElement('div');
        overlay.style.cssText = [
            'position:fixed;inset:0;z-index:10000001;',
            'background:rgba(0,0,0,0.45);',
            'backdrop-filter:blur(4px);-webkit-backdrop-filter:blur(4px);',
            'display:flex;align-items:center;justify-content:center;',
        ].join('');
        var card = document.createElement('div');
        card.style.cssText = [
            'background:rgba(255,255,255,0.92);',
            'backdrop-filter:blur(20px);-webkit-backdrop-filter:blur(20px);',
            'border:1px solid rgba(255,255,255,0.6);',
            'border-radius:16px;padding:24px 28px;',
            'box-shadow:0 16px 48px rgba(0,0,0,0.22);',
            'font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Arial,sans-serif;',
            'color:#1a1a2e;max-width:400px;min-width:260px;',
        ].join('');
        var titleEl = document.createElement('div');
        titleEl.textContent = title;
        titleEl.style.cssText = 'font-weight:700;font-size:14px;margin-bottom:10px;';
        card.appendChild(titleEl);
        var msgEl = document.createElement('div');
        msgEl.textContent = message;
        msgEl.style.cssText = 'font-size:13px;color:#444;white-space:pre-wrap;margin-bottom:18px;line-height:1.5;max-height:60vh;overflow-y:auto;';
        card.appendChild(msgEl);
        var btnRow = document.createElement('div');
        btnRow.style.cssText = 'display:flex;gap:8px;justify-content:flex-end;';
        buttons.forEach(function (b) {
            var btn = document.createElement('button');
            btn.textContent = b.label;
            btn.style.cssText = [
                'padding:7px 18px;border:none;border-radius:8px;',
                'font-size:12.5px;font-weight:600;cursor:pointer;transition:opacity .15s;',
                'background:' + (b.primary ? '#4a90e2' : 'rgba(0,0,0,0.08)') + ';',
                'color:' + (b.primary ? '#fff' : '#1a1a2e') + ';',
            ].join('');
            btn.addEventListener('mouseenter', function () { btn.style.opacity = '0.8'; });
            btn.addEventListener('mouseleave', function () { btn.style.opacity = '1'; });
            btn.addEventListener('click', function () {
                if (overlay.parentNode) overlay.parentNode.removeChild(overlay);
                if (b.action) b.action();
            });
            btnRow.appendChild(btn);
        });
        card.appendChild(btnRow);
        overlay.appendChild(card);
        document.body.appendChild(overlay);
    }

    function showAlert(title, message, onOk) {
        showModal(title, message, [{ label: 'OK', primary: true, action: onOk || null }]);
    }

    function showConfirm(title, message, onConfirm) {
        showModal(title, message, [
            { label: 'Abbrechen', primary: false },
            { label: 'Bestaetigen', primary: true, action: onConfirm },
        ]);
    }

    var lastUrl      = '';
    var panelBuilt   = false;
    var progressBar  = null;
    var activeInterval = null;

    setInterval(function () {
        var url = window.location.href;
        if (url === lastUrl) return;
        lastUrl = url;
        if (activeInterval) { clearInterval(activeInterval); activeInterval = null; }
        if (window.location.pathname.includes('/lessonDetails/')) {
            onLessonDetailsPage();
        } else {
            onTimetablePage();
        }
    }, 300);

    function onLessonDetailsPage() {
        if (!GM_getValue('scrapingMode', false)) return;
        var tries = 0;
        activeInterval = setInterval(function () {
            tries++;
            if (document.querySelector('.empty-indicator.empty-indicator--without-card')) {
                clearInterval(activeInterval); activeInterval = null;
                saveAndGoBack('');
                return;
            }
            var labels = document.querySelectorAll('.inline-text-input label');
            for (var i = 0; i < labels.length; i++) {
                if (labels[i].textContent.trim() === 'Lehrstoff') {
                    var container = labels[i].closest('.inline-text-input');
                    var ta = container && container.querySelector('textarea');
                    if (ta) {
                        clearInterval(activeInterval); activeInterval = null;
                        saveAndGoBack(ta.value || ta.textContent.trim());
                        return;
                    }
                }
            }
            if (tries >= 30) { clearInterval(activeInterval); activeInterval = null; saveAndGoBack(''); }
        }, 500);
    }

    function saveAndGoBack(lehrstoff) {
        if (lehrstoff && lehrstoff.trim()) {
            var subject = GM_getValue('currentSubject', '?');
            var results = JSON.parse(GM_getValue('scrapingResults', '[]'));
            var parts   = window.location.pathname.split('/');
            results.push({
                subject:   subject,
                lehrstoff: lehrstoff.trim(),
                startTime: decodeURIComponent(parts[7] || ''),
                endTime:   decodeURIComponent(parts[8] || '')
            });
            GM_setValue('scrapingResults', JSON.stringify(results));
        }
        history.back();
    }

    function onTimetablePage() {
        if (GM_getValue('scrapingMode', false)) {
            var idx   = GM_getValue('scrapingIndex', 0);
            var total = GM_getValue('scrapingTotal', 0);
            if (idx < total) {
                setProgressBar(idx, total);
                clickCard(idx);
            } else {
                GM_setValue('scrapingMode', false);
                removeProgressBar();
                rebuildPanel();
                var results = JSON.parse(GM_getValue('scrapingResults', '[]'));
                if (results.length > 0) {
                    setTimeout(function () {
                        var text = buildText(results);
                        showModal('Scraping fertig', results.length + ' Lektionen:\n\n' + text, [
                            {
                                label: 'Alles kopieren',
                                primary: true,
                                action: function () {
                                    navigator.clipboard.writeText(text);
                                }
                            },
                            { label: 'Schließen', primary: false }
                        ]);
                    }, 400);
                }
            }
        } else {
            if (!panelBuilt) { panelBuilt = true; buildTimetablePanel(); }
        }
    }

    function clickCard(idx) {
        var tries = 0;
        activeInterval = setInterval(function () {
            tries++;
            var cards = document.querySelectorAll('.timetable-grid-card .lesson-card.clickable');
            if (cards.length > idx) {
                clearInterval(activeInterval); activeInterval = null;
                var card  = cards[idx];
                var subEl = card.querySelector('[data-testid="lesson-card-resources-with-change-subject"] [data-testid="regular-resource"]');
                GM_setValue('currentSubject', subEl ? subEl.textContent.trim() : ('Lektion ' + (idx + 1)));
                GM_setValue('scrapingIndex', idx + 1);
                card.click();
            }
            if (tries > 40) {
                clearInterval(activeInterval); activeInterval = null;
                GM_setValue('scrapingMode', false);
                removeProgressBar();
                showAlert('Scraping abgebrochen', 'Karte ' + (idx + 1) + ' nicht gefunden.');
                rebuildPanel();
            }
        }, 500);
    }

    function setProgressBar(current, total) {
        if (!progressBar) {
            var overlay = document.createElement('div');
            overlay.style.cssText = [
                'position:fixed;inset:0;z-index:9999998;',
                'background:rgba(0,0,0,0.45);',
                'backdrop-filter:blur(4px);-webkit-backdrop-filter:blur(4px);',
            ].join('');
            var card = document.createElement('div');
            card.style.cssText = [
                'position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);',
                'z-index:9999999;background:rgba(255,255,255,0.88);',
                'backdrop-filter:blur(20px);-webkit-backdrop-filter:blur(20px);',
                'border:1px solid rgba(255,255,255,0.6);border-radius:16px;padding:28px 32px;',
                'box-shadow:0 16px 48px rgba(0,0,0,0.22);',
                'font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Arial,sans-serif;',
                'color:#1a1a2e;text-align:center;min-width:220px;',
            ].join('');
            var spinWrap = document.createElement('div');
            spinWrap.style.cssText = 'display:flex;justify-content:center;margin-bottom:16px;';
            var spinSvg = icon(ICONS.loader, 36);
            spinSvg.style.cssText += 'animation:__scraper_spin 1s linear infinite;color:#4a90e2;';
            if (!document.getElementById('__scraper_keyframes')) {
                var style = document.createElement('style');
                style.id = '__scraper_keyframes';
                style.textContent = '@keyframes __scraper_spin{from{transform:rotate(0deg)}to{transform:rotate(360deg)}}';
                document.head.appendChild(style);
            }
            spinWrap.appendChild(spinSvg);
            card.appendChild(spinWrap);
            var lbl = document.createElement('div');
            lbl.style.cssText = 'font-size:14px;font-weight:600;margin-bottom:6px;';
            card._label = lbl;
            card.appendChild(lbl);
            var sub = document.createElement('div');
            sub.style.cssText = 'font-size:12px;color:#888;margin-bottom:6px;';
            sub.textContent = 'Bitte verlassen Sie diesen Tab nicht…';
            card.appendChild(sub);
            var hint = document.createElement('div');
            hint.style.cssText = 'font-size:11px;color:#bbb;margin-bottom:18px;';
            hint.textContent = 'Sollten Sie nicht weiterkommen, drücken Sie bitte die ESC-Taste oder warten Sie ein bisschen.';
            card.appendChild(hint);
            var cancelBtn = document.createElement('button');
            cancelBtn.style.cssText = [
                'background:rgba(0,0,0,0.07);color:#1a1a2e;',
                'border:1px solid rgba(0,0,0,0.14);border-radius:8px;',
                'padding:6px 16px;font-size:12px;font-weight:600;cursor:pointer;',
                'display:inline-flex;align-items:center;gap:6px;transition:background .15s;',
            ].join('');
            cancelBtn.appendChild(icon(ICONS.xmark, 13));
            var cspan = document.createElement('span');
            cspan.textContent = 'Abbrechen';
            cancelBtn.appendChild(cspan);
            cancelBtn.addEventListener('mouseenter', function () { cancelBtn.style.background = 'rgba(0,0,0,0.13)'; });
            cancelBtn.addEventListener('mouseleave', function () { cancelBtn.style.background = 'rgba(0,0,0,0.07)'; });
            cancelBtn.addEventListener('click', function () {
                if (activeInterval) { clearInterval(activeInterval); activeInterval = null; }
                GM_setValue('scrapingMode', false);
                GM_setValue('scrapingResults', '[]');
                removeProgressBar();
                rebuildPanel();
            });
            card.appendChild(cancelBtn);
            overlay.appendChild(card);
            document.body.appendChild(overlay);
            progressBar = overlay;
            progressBar._label = lbl;
        }
        progressBar._label.textContent = 'Scraping ' + (current + 1) + ' / ' + total;
    }

    function removeProgressBar() {
        if (progressBar && progressBar.parentNode) {
            progressBar.parentNode.removeChild(progressBar);
            progressBar = null;
        }
    }

    function rebuildPanel() {
        ['__scraperToggle', '__scraperPanel'].forEach(function (id) {
            var el = document.getElementById(id);
            if (el) el.parentNode.removeChild(el);
        });
        panelBuilt = true;
        buildTimetablePanel();
    }

    function buildTimetablePanel() {
        var overlay = document.createElement('div');
        overlay.id = '__scraperPanel';
        overlay.style.cssText = [
            'position:fixed;inset:0;z-index:999999;',
            'background:rgba(0,0,0,0.45);',
            'backdrop-filter:blur(4px);-webkit-backdrop-filter:blur(4px);',
            'display:none;align-items:center;justify-content:center;',
        ].join('');
        overlay.addEventListener('click', function (e) {
            if (e.target === overlay) overlay.style.display = 'none';
        });

        var card = document.createElement('div');
        card.style.cssText = [
            'background:rgba(255,255,255,0.92);',
            'backdrop-filter:blur(20px);-webkit-backdrop-filter:blur(20px);',
            'border:1px solid rgba(255,255,255,0.6);',
            'border-radius:16px;padding:22px 24px 20px;',
            'box-shadow:0 16px 48px rgba(0,0,0,0.22);',
            'font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Arial,sans-serif;',
            'font-size:13px;color:#1a1a2e;min-width:260px;max-width:320px;',
            'position:relative;',
        ].join('');

        var xBtn = document.createElement('button');
        xBtn.title = 'Schließen';
        xBtn.style.cssText = [
            'position:absolute;top:12px;right:12px;',
            'background:rgba(0,0,0,0.06);border:none;border-radius:50%;',
            'width:26px;height:26px;cursor:pointer;padding:0;',
            'display:flex;align-items:center;justify-content:center;',
            'transition:background .15s;color:#555;',
        ].join('');
        xBtn.appendChild(icon(ICONS.xmark, 13));
        xBtn.addEventListener('mouseenter', function () { xBtn.style.background = 'rgba(0,0,0,0.13)'; });
        xBtn.addEventListener('mouseleave', function () { xBtn.style.background = 'rgba(0,0,0,0.06)'; });
        xBtn.addEventListener('click', function () { overlay.style.display = 'none'; });
        card.appendChild(xBtn);

        var helpBtn = document.createElement('button');
        helpBtn.title = 'Info';
        helpBtn.style.cssText = [
            'position:absolute;top:12px;right:46px;',
            'background:rgba(0,0,0,0.06);border:none;border-radius:50%;',
            'width:26px;height:26px;cursor:pointer;padding:0;',
            'display:flex;align-items:center;justify-content:center;',
            'transition:background .15s;color:#555;',
            'font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Arial,sans-serif;',
            'font-size:13px;font-weight:700;',
        ].join('');
        helpBtn.textContent = '?';
        helpBtn.addEventListener('mouseenter', function () { helpBtn.style.background = 'rgba(0,0,0,0.13)'; });
        helpBtn.addEventListener('mouseleave', function () { helpBtn.style.background = 'rgba(0,0,0,0.06)'; });
        helpBtn.addEventListener('click', function () {
            showAlert('WebUntis Topic Scraper',
                'Eine inoffizielle Erweiterung für WebUntis, die die Lehrstoffe und Themen aus allen Unterrichtsstunden der jeweiligen Schulwoche automatisch herausliest.\n\n' +
                'So funktioniert es:\n' +
                '1. Zur gewünschten Schulwoche in WebUntis wechseln.\n' +
                '2. Den WebUntis Topic Scraper öffnen und auf "Scrapen starten" klicken.\n' +
                '3. Das Script klickt automatisch jede Unterrichtsstunde durch und liest den Lehrstoff aus.\n' +
                '4. Die Ergebnisse können danach kopiert und ins Berichtsheft eingefügt werden.\n\n' +
                'Projekt umgesetzt von Whiztech.'
            );
        });
        card.appendChild(helpBtn);

        //Title
        var titleDiv = document.createElement('div');
        titleDiv.textContent = 'WebUntis Topic Scraper';
        titleDiv.style.cssText = 'font-weight:700;font-size:13px;margin-bottom:14px;color:#1a1a2e;letter-spacing:0.01em;padding-right:60px;';
        card.appendChild(titleDiv);

        //Scrape button
        var scrapeBtn = makeBtn('Scrapen starten', 'play', '#4a90e2', '#357abd');
        scrapeBtn.style.marginBottom = '0';
        scrapeBtn.addEventListener('click', function () {
            var cards = document.querySelectorAll('.timetable-grid-card .lesson-card.clickable');
            if (cards.length === 0) { showAlert('Hinweis', 'Keine Lektionen gefunden.'); return; }
            GM_setValue('scrapingMode', true);
            GM_setValue('scrapingTotal', cards.length);
            GM_setValue('scrapingIndex', 1);
            GM_setValue('scrapingResults', '[]');
            var firstSubEl = cards[0].querySelector('[data-testid="lesson-card-resources-with-change-subject"] [data-testid="regular-resource"]');
            GM_setValue('currentSubject', firstSubEl ? firstSubEl.textContent.trim() : 'Lektion 1');
            overlay.style.display = 'none';
            setProgressBar(0, cards.length);
            cards[0].click();
        });
        card.appendChild(scrapeBtn);

        //Stored results section
        var stored = JSON.parse(GM_getValue('scrapingResults', '[]'));
        if (stored.length > 0) {
            card.appendChild(sep());
            var lbl = document.createElement('div');
            lbl.textContent = stored.length + ' Ergebnisse gespeichert';
            lbl.style.cssText = 'font-size:11.5px;color:#666;margin-bottom:8px;';
            card.appendChild(lbl);

            var clipBtn = makeBtn('Alles kopieren', 'copy', '#2ecc71', '#27ae60');
            clipBtn.addEventListener('click', function () {
                navigator.clipboard.writeText(buildText(stored)).then(function () {
                    clipBtn._span.textContent = 'Kopiert!';
                    clipBtn.replaceChild(icon(ICONS.check, 14), clipBtn.firstChild);
                    clipBtn.style.background = '#1a7a44';
                    setTimeout(function () {
                        clipBtn._span.textContent = 'Alles kopieren';
                        clipBtn.replaceChild(icon(ICONS.copy, 14), clipBtn.firstChild);
                        clipBtn.style.background = '#2ecc71';
                    }, 2000);
                });
            });
            card.appendChild(clipBtn);

            var showBtn = makeBtn('Ergebnisse anzeigen', 'eye', '#f39c12', '#d68910');
            showBtn.addEventListener('click', function () {
                showAlert('Gespeicherte Ergebnisse', stored.length + ' Lektionen:\n\n' + buildText(stored));
            });
            card.appendChild(showBtn);

            var clearBtn = makeBtn('Speicher leeren', 'trash', '#e74c3c', '#c0392b');
            clearBtn.style.marginBottom = '0';
            clearBtn.addEventListener('click', function () {
                showConfirm('Speicher leeren', 'Gespeicherte Ergebnisse wirklich löschen?', function () {
                    GM_setValue('scrapingResults', '[]');
                    rebuildPanel();
                });
            });
            card.appendChild(clearBtn);
        }

        overlay.appendChild(card);
        document.body.appendChild(overlay);

        var headerPollInterval = setInterval(function () {
            var controls = document.querySelector('.timetable-header-controls');
            if (!controls) return;
            if (document.getElementById('__scraperToggle')) return;
            clearInterval(headerPollInterval);

            var toggleBtn = document.createElement('button');
            toggleBtn.id = '__scraperToggle';
            toggleBtn.title = 'WebUntis Topic Scraper';
            toggleBtn.style.cssText = [
                'background:#4a90e2;color:#fff;border:none;border-radius:6px;',
                'padding:0 10px;height:28px;cursor:pointer;margin-right:8px;',
                'box-shadow:0 2px 8px rgba(0,0,0,0.12);transition:opacity .2s;',
                'display:inline-flex;align-items:center;gap:6px;',
                'font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Arial,sans-serif;',
                'font-size:12px;font-weight:600;vertical-align:middle;',
            ].join('');
            toggleBtn.appendChild(icon(ICONS.clipboard, 14));
            var tlbl = document.createElement('span');
            tlbl.textContent = 'Scraper';
            toggleBtn.appendChild(tlbl);
            toggleBtn.addEventListener('mouseenter', function () { toggleBtn.style.opacity = '0.82'; });
            toggleBtn.addEventListener('mouseleave', function () { toggleBtn.style.opacity = '1'; });
            controls.insertBefore(toggleBtn, controls.firstChild);

            toggleBtn.addEventListener('click', function () {
                overlay.style.display = overlay.style.display === 'flex' ? 'none' : 'flex';
            });
        }, 200);
    }

    function buildText(results) {
        var grouped = {};
        var order = [];
        results.forEach(function (r) {
            var key = r.subject || '?';
            if (!grouped[key]) { grouped[key] = []; order.push(key); }
            grouped[key].push(r.lehrstoff);
        });
        return order.map(function (key) {
            return key + ': ' + grouped[key].join('; ');
        }).join('\n');
    }

})();