Weiter zum Inhalt
  • Home
  • Aktuell
  • 0 Ungelesen 0
  • Kategorien
  • Unreplied
  • Beliebt
  • GitHub
  • Docu
  • Hilfe
Skins
  • Hell
  • Brite
  • Cerulean
  • Cosmo
  • Flatly
  • Journal
  • Litera
  • Lumen
  • Lux
  • Materia
  • Minty
  • Morph
  • Pulse
  • Sandstone
  • Simplex
  • Sketchy
  • Spacelab
  • United
  • Yeti
  • Zephyr
  • Dunkel
  • Cyborg
  • Darkly
  • Quartz
  • Slate
  • Solar
  • Superhero
  • Vapor

  • Standard: (Kein Skin)
  • Kein Skin
Einklappen
ioBroker Logo

Community Forum

donate donate
  1. ioBroker Community Home
  2. Deutsch
  3. Tester
  4. TESTER: Neuer Adapter Webuntis

NEWS

  • Neuer Installer und neuer Fixer: ĂŒbersichtliche OberflĂ€che und `iob docker`
    BluefoxB
    Bluefox
    7
    1
    274

  • Mitmachen: Sprich „ioBroker“ ein – fĂŒr ein lokales Aktivierungswort
    BluefoxB
    Bluefox
    6
    1
    281

  • Neuer Blogbeitrag: KI in ioBroker, vom Helfer im Admin bis zum eigenen Smart Assistant
    BluefoxB
    Bluefox
    4
    1
    263

TESTER: Neuer Adapter Webuntis

Geplant Angeheftet Gesperrt Verschoben Tester
201 BeitrÀge 53 Kommentatoren 67.8k Aufrufe 46 Beobachtet
  • Älteste zuerst
  • Neuste zuerst
  • Meiste Stimmen
Antworten
  • In einem neuen Thema antworten
Anmelden zum Antworten
Dieses Thema wurde gelöscht. Nur Nutzer mit entsprechenden Rechten können es sehen.
  • madingM
    madingM
    mading
    schrieb am zuletzt editiert von mading
    #192

    Edit

    Der Adapter ruft bei mir nur die nĂ€chsten zwei Tage ab, ĂŒber die App sehe ich zb die gesamte Woche. Kann man das erweitern?

    1 Antwort Letzte Antwort
    0
    • T
      T
      tobi_kieninger
      schrieb am zuletzt editiert von
      #193

      Hallo zusammen,

      ich bin auch Grade neu zum Adapter hinzugekommen.
      Leider hat Untis die Serveradressen und vermutlich auch die API geÀndert.
      Weiss da jemand mehr?

      madingM 1 Antwort Letzte Antwort
      0
      • T tobi_kieninger

        Hallo zusammen,

        ich bin auch Grade neu zum Adapter hinzugekommen.
        Leider hat Untis die Serveradressen und vermutlich auch die API geÀndert.
        Weiss da jemand mehr?

        madingM
        madingM
        mading
        schrieb am zuletzt editiert von
        #194

        @tobi_kieninger sagte in TESTER: Neuer Adapter Webuntis:

        Hallo zusammen,

        ich bin auch Grade neu zum Adapter hinzugekommen.
        Leider hat Untis die Serveradressen und vermutlich auch die API geÀndert.
        Weiss da jemand mehr?

        Bei mir hat sich seit Einrichtung letzten Sept die Base URL und das Secret geÀndert. Die Daten kommen aber an

        T 1 Antwort Letzte Antwort
        0
        • madingM mading

          @tobi_kieninger sagte in TESTER: Neuer Adapter Webuntis:

          Hallo zusammen,

          ich bin auch Grade neu zum Adapter hinzugekommen.
          Leider hat Untis die Serveradressen und vermutlich auch die API geÀndert.
          Weiss da jemand mehr?

          Bei mir hat sich seit Einrichtung letzten Sept die Base URL und das Secret geÀndert. Die Daten kommen aber an

          T
          T
          tobi_kieninger
          schrieb am zuletzt editiert von
          #195

          @mading sagte in TESTER: Neuer Adapter Webuntis:

          @tobi_kieninger sagte in TESTER: Neuer Adapter Webuntis:

          Hallo zusammen,

          ich bin auch Grade neu zum Adapter hinzugekommen.
          Leider hat Untis die Serveradressen und vermutlich auch die API geÀndert.
          Weiss da jemand mehr?

          Bei mir hat sich seit Einrichtung letzten Sept die Base URL und das Secret geÀndert. Die Daten kommen aber an

          In wieweit hat sich das Secret geÀndert?

          Ich. In seit 7.12. abgeschnitten..

          Die neue BaseUrl enthÀlt jetzt den Schulnamen, das hab ich geschafft, aber wie Àndert sich das Secret?

          1 Antwort Letzte Antwort
          0
          • madingM
            madingM
            mading
            schrieb am zuletzt editiert von
            #196

            Ist auf der git-Seite erklÀrt was was ist

            1 Antwort Letzte Antwort
            0
            • EisbaeeerE Eisbaeeer

              Ich habe eben mal den Adapter installiert und bekomme sehr wenig Infos:
              e9fca9c2-878b-487d-ac10-a50860af6670-image.png

              Im Log steht, dass er den Kalender nicht abrufen kann. In der WeboberflÀche sehe ich den Kalender aber.

              --- Edit ---
              Ok, mit dem Account vom SchĂŒler bekomme ich den Stundenplan (macht irgendwie auch Sinn).
              Leider sind im Adapter die Klassenarbeiten nicht ersichtlich. Hat da schon jemand eine Lösung?
              VG Lars

              Tg-71T
              Tg-71T
              Tg-71
              Forum Testing
              schrieb am zuletzt editiert von
              #197

              @Eisbaeeer
              Das mit dem SchĂŒler Account hat geholfen - Danke - Hatte es erst mit dem Elternaccount probiert

              1 Antwort Letzte Antwort
              0
              • M
                M
                mrMuppet
                schrieb am zuletzt editiert von
                #198

                Bei uns benutzen alle SchĂŒler und Eltern(!) den gleichen Login und man sucht sich unter "weitere StundenplĂ€ne" die entsprechende Klasse(n) aus.
                LĂ€sst sich das auch abfragen?
                Aktuell bekomm ich nur "Cannot read Timetable for today - possible block by scool"

                ioBroker auf NUC (Celeron mit Ubuntu-Server)

                Homematic, HMIP, Hue, Unifi, Plex, Nest, Roborock, Google Assistant

                1 Antwort Letzte Antwort
                0
                • Bass-TB
                  Bass-TB
                  Bass-T
                  schrieb am zuletzt editiert von
                  #199

                  Hi alle zusammen,
                  nutze den Adapter nun auch, nachdem mit die Schule mitgeteilt hat, dass ical export ein kostenpflichtiger zusatz ist, und die Schule dass daher nicht anbieten kann.

                  Adapter installiert und mit SchĂŒler Account Verbunden.

                  Alles super.

                  WĂŒnsche:
                  Wie wÀre ein Datenpunkt mit komplett input als rohdaten?

                  Hintergrund:
                  Ich möchte gerne den Stundenplan als export fĂŒr JARVIS nutzen. Mache das aktuell schon mit Edupage meiner GrundschĂŒler.
                  Weiter hÀtte ich gerne die gesammte aktuelle Woche inkl. Wichtiger news (wenn möglich als separaten datenpunkt)

                  Gerne unterstĂŒtze ich auch dabei ;)

                  GrĂŒĂŸe

                  Proxmox Cluster

                  • NUC7i5 ioBroker Server (Proxmox)
                  • HP Elitedesk 800 G4 SSF (Proxmox)
                  • HP Elitedesk 800 G2 DM (Proxmox)
                    mein Proxmox-Updater fĂŒr euch

                  KNX / Broadlink / Tasmota ESPs / Zigbee
                  Vis: 15" Touchmonitor mit Raspi4 / 10" Tablet (Dauerstrom umbau) / Sonoff NSPanel (alle)

                  1 Antwort Letzte Antwort
                  0
                  • madingM
                    madingM
                    mading
                    schrieb am zuletzt editiert von mading
                    #200

                    Hi zusammen,

                    der Adapter war mir zu unzuverlĂ€ssig bzw. hatte ich ein Javascript gefunden, dass mir alles fĂŒr vis in einer Tabelle aufarbeitet. Das war aber nicht immer richtig. Ich habe mit Hilfe von copilot ein Skript gebaut, das mir Screenshots des Stundenplans macht.

                    
                    # Create a new folder for the script
                    mkdir webuntis-shot && cd webuntis-shot
                    
                    # Initialize and install dependencies
                    npm init -y
                    npm i playwright dotenv
                    
                    # Install browsers for Playwright (first time only)
                    npx playwright install
                    
                    

                    dann .env Datei erstellen

                    
                    # .env
                    WEBUNTIS_USERNAME="user"
                    WEBUNTIS_PASSWORD="xyz"
                    HEADLESS="1"
                    OUTPUT_DIR="/opt/iobroker"
                    
                    

                    dann die Datei z.B. webuntis.js anlegen

                    // webuntis-screenshot.js
                    // Usage: node webuntis-screenshot.js
                    // Optional env: WEBUNTIS_USERNAME, WEBUNTIS_PASSWORD, HEADLESS, OUTPUT_DIR, TARGET_HASH_FILE
                    
                    import { chromium } from 'playwright';
                    import fs from 'fs';
                    import fsp from 'fs/promises';
                    import path from 'path';
                    import 'dotenv/config';
                    
                    const USERNAME = process.env.WEBUNTIS_USERNAME || 'user';
                    const PASSWORD = process.env.WEBUNTIS_PASSWORD || 'pass';
                    const HEADLESS = (process.env.HEADLESS || '1') !== '0';
                    const OUTPUT_DIR = process.env.OUTPUT_DIR || '/opt/iobroker/iobroker-data/files/vis.0/main/img/';
                    
                    const BASE_URL = 'https://gss-realschule.webuntis.com/timetable/my-student?date=';
                    
                    // === NEW: read TARGET_HASH from external file ===
                    const TARGET_HASH_FILE = process.env.TARGET_HASH_FILE || path.resolve(process.cwd(), 'target-hash.txt');
                    
                    async function loadTargetHash(filePath) {
                      const exists = fs.existsSync(filePath);
                      if (!exists) {
                        throw new Error(`Target-hash file not found: ${filePath}`);
                      }
                    
                      const ext = path.extname(filePath).toLowerCase();
                    
                      if (ext === '.json') {
                        const raw = await fsp.readFile(filePath, 'utf-8');
                        let data;
                        try {
                          data = JSON.parse(raw);
                        } catch (e) {
                          throw new Error(`Invalid JSON in ${filePath}: ${e.message}`);
                        }
                        const hash = (data.TARGET_HASH || '').trim();
                        if (!hash) throw new Error(`Missing "TARGET_HASH" property in ${filePath}`);
                        validateHash(hash);
                        return hash;
                      } else {
                        // treat as plain text
                        const hash = (await fsp.readFile(filePath, 'utf-8')).trim();
                        validateHash(hash);
                        return hash;
                      }
                    }
                    
                    function validateHash(hash) {
                      if (!hash.startsWith('#/')) {
                        throw new Error(`TARGET_HASH must start with "#/". Got: "${hash}"`);
                      }
                    }
                    
                    // Utility: ensure directory exists (try to create if not)
                    function ensureDir(dir) {
                      try {
                        if (!fs.existsSync(dir)) {
                          fs.mkdirSync(dir, { recursive: true });
                        }
                        const testFile = path.join(dir, '.test_write.tmp');
                        fs.writeFileSync(testFile, 'ok');
                        fs.unlinkSync(testFile);
                        return true;
                      } catch (e) {
                        console.error(`[WARN] Cannot write to ${dir}: ${e.message}`);
                        return false;
                      }
                    }
                    
                    async function tryFill(page, selectors, value, opts = {}) {
                      for (const sel of selectors) {
                        try {
                          const el = await page.$(sel);
                          if (el) {
                            await el.fill(value, { timeout: opts.timeout || 3000 });
                            return true;
                          }
                        } catch { /* continue */ }
                      }
                      return false;
                    }
                    
                    async function tryClick(page, candidates, opts = {}) {
                      for (const c of candidates) {
                        try {
                          if (c.selector) {
                            await page.click(c.selector, { timeout: opts.timeout || 3000 });
                            return true;
                          }
                          if (c.text) {
                            const el = page.getByText(c.text, { exact: false });
                            await el.first().click({ timeout: opts.timeout || 3000 });
                            return true;
                          }
                        } catch { /* continue */ }
                      }
                      return false;
                    }
                    
                    async function acceptCookies(page) {
                      const candidates = [
                        { text: 'Accept all' }, { text: 'Accept All' }, { text: 'Accept' },
                        { text: 'Alle akzeptieren' }, { text: 'Akzeptieren' }, { text: 'Einverstanden' },
                        { selector: 'button#onetrust-accept-btn-handler' },
                        { selector: 'button[aria-label*="accept"]' },
                      ];
                      await tryClick(page, candidates, { timeout: 2000 });
                    }
                    
                    async function loginIfNeeded(page, username, password) {
                      const usernameSelectors = [
                        'input[name="username"]',
                        'input#username',
                        'input[name="user"]',
                        'input[type="text"]',
                        'input[autocomplete="username"]'
                      ];
                      const passwordSelectors = [
                        'input[name="password"]',
                        'input#password',
                        'input[type="password"]',
                        'input[autocomplete="current-password"]'
                      ];
                      const loginButtonCandidates = [
                        { text: 'Log in' }, { text: 'Login' }, { text: 'Anmelden' }, { text: 'Sign in' },
                        { selector: 'button[type="submit"]' }
                      ];
                    
                      let needsLogin = false;
                      for (const sel of passwordSelectors) {
                        const el = await page.$(sel);
                        if (el) { needsLogin = true; break; }
                      }
                    
                      if (!needsLogin) {
                        const clicked = await tryClick(page, [{ text: 'Login' }, { text: 'Anmelden' }, { text: 'Log in' }], { timeout: 2000 });
                        if (clicked) {
                          await page.waitForTimeout(1200);
                        }
                        for (const sel of passwordSelectors) {
                          const el = await page.$(sel);
                          if (el) { needsLogin = true; break; }
                        }
                      }
                    
                      if (!needsLogin) return false;
                    
                      const uFilled = await tryFill(page, usernameSelectors, username);
                      const pFilled = await tryFill(page, passwordSelectors, password);
                      if (!uFilled || !pFilled) {
                        throw new Error('Could not locate username/password fields to perform login.');
                      }
                    
                      const clickedSubmit = await tryClick(page, loginButtonCandidates, { timeout: 3000 });
                      if (!clickedSubmit) {
                        for (const sel of passwordSelectors) {
                          const el = await page.$(sel);
                          if (el) {
                            await el.press('Enter');
                            break;
                          }
                        }
                      }
                    
                      await page.waitForLoadState('domcontentloaded');
                      await page.waitForTimeout(2000);
                      return true;
                    }
                    
                    function timestamp() {
                      const d = new Date();
                      const pad = (n) => String(n).padStart(2, '0');
                      return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}_${pad(d.getHours())}-${pad(d.getMinutes())}-${pad(d.getSeconds())}`;
                    }
                    
                    (async () => {
                      console.log('[INFO] Starting WebUntis screenshot task...');
                      const canWrite = ensureDir(OUTPUT_DIR);
                      const outDir = canWrite ? OUTPUT_DIR : process.cwd();
                      if (!canWrite) {
                        console.warn(`[WARN] Falling back to current directory: ${outDir}`);
                      }
                    
                      // Load external hash
                      let TARGET_HASH = '';
                      try {
                        TARGET_HASH = await loadTargetHash(TARGET_HASH_FILE);
                        console.log(`[INFO] Loaded TARGET_HASH from ${TARGET_HASH_FILE}: ${TARGET_HASH}`);
                      } catch (e) {
                        console.error('[ERROR] Failed to load TARGET_HASH:', e.message);
                        process.exit(1);
                      }
                    
                      const TARGET_URL = `${BASE_URL}${TARGET_HASH}`;
                    
                      const browser = await chromium.launch({ headless: HEADLESS });
                      const context = await browser.newContext({ viewport: { width: 1400, height: 900 } });
                      const page = await context.newPage();
                    
                      try {
                        console.log('[INFO] Navigating to base URL
');
                        await page.goto(BASE_URL, { waitUntil: 'domcontentloaded', timeout: 60000 });
                        await acceptCookies(page);
                    
                        await loginIfNeeded(page, USERNAME, PASSWORD);
                    
                        console.log('[INFO] Navigating to target timetable URL
');
                        await page.goto(TARGET_URL, { waitUntil: 'domcontentloaded', timeout: 60000 });
                    
                        await page.waitForTimeout(4000);
                        await acceptCookies(page);
                    
                        const filePath = path.join(outDir, `webuntis-screenshot.png`);
                        await page.screenshot({ path: filePath, fullPage: true });
                        console.log(`[SUCCESS] Screenshot saved to: ${filePath}`);
                      } catch (err) {
                        console.error('[ERROR]', err.message);
                        process.exitCode = 1;
                      } finally {
                        await context.close();
                        await browser.close();
                      }
                    })();
                    
                    

                    eigentlich dachte ich, ich muss die URL noch wöchentlich auf die richtige Woche Àndern, das macht aber ein redirect. die target-hash.txt Datei ist aber geblieben (gleicher Ordner) - steht "nichts" drin, könnte geÀndert werden

                    #/
                    
                    

                    dann das Skript laufen lassen

                    node webuntis.js
                    

                    Bei mir kamen noch ein paar Fehler, ich musste ein paar libraries nachinstallieren.

                    Somit lÀuft es bei mir, ich muss jetzt nur noch schauen, ob ich es mit einem blockly und cron triggere oder von debian aus.

                    494470b8-8e71-4663-8202-0716112cdf72-grafik.png

                    Damit lĂ€uft das Skript stĂŒndlich
                    0 * * * * /usr/bin/node /home/mading/webuntis-shot/webuntis.js >> /home/mading/webuntis-shot/cron.log 2>&1

                    Damit vis das Bild kennt bzw. lesen kann, ist ein Javascript notwendig, dass die Datei in vis.0 schreibt:

                    var fs = require('fs'); 
                     
                     const picture= fs.readFileSync('/opt/iobroker/iobroker-data/files/vis.0/main/img/webuntis-screenshot.png'); //liest linux-datei-system
                    writeFile('vis.0','/main/img/webuntis-screenshot.png', picture, function (error) { });   //schreibt in iobroker system
                    

                    Blockly:

                    <xml xmlns="https://developers.google.com/blockly/xml">
                      <block type="procedures_defcustomnoreturn" id="FuE%6{wZt7R{vL]0sp=:" x="-37" y="38">
                        <mutation statements="false"></mutation>
                        <field name="NAME">etwas tun</field>
                        <field name="SCRIPT">dmFyIGZzID0gcmVxdWlyZSgnZnMnKTsgDQogDQogY29uc3QgcGljdHVyZT0gZnMucmVhZEZpbGVTeW5jKCcvb3B0L2lvYnJva2VyL2lvYnJva2VyLWRhdGEvZmlsZXMvdmlzLjAvbWFpbi9pbWcvd2VidW50aXMtc2NyZWVuc2hvdC5wbmcnKTsgLy9saWVzdCBsaW51eC1kYXRlaS1zeXN0ZW0NCndyaXRlRmlsZSgndmlzLjAnLCcvbWFpbi9pbWcvd2VidW50aXMtc2NyZWVuc2hvdC5wbmcnLCBwaWN0dXJlLCBmdW5jdGlvbiAoZXJyb3IpIHsgfSk7ICAgLy9zY2hyZWlidCBpbiBpb2Jyb2tlciBzeXN0ZW0=</field>
                        <comment pinned="false" h="80" w="160">Beschreibe diese Funktion 
</comment>
                      </block>
                      <block type="schedule" id="9DD.a-@UNvoRp8i:Yode" x="63" y="113">
                        <field name="SCHEDULE">{"time":{"start":"06:00","end":"23:00","mode":"minutes","interval":61},"period":{"days":1}}</field>
                        <statement name="STATEMENT">
                          <block type="procedures_callcustomnoreturn" id="esc^G_dpBegT:H7{mULt">
                            <mutation name="etwas tun"></mutation>
                          </block>
                        </statement>
                      </block>
                    </xml>
                    
                    1 Antwort Letzte Antwort
                    0
                    • HomoranH Homoran verschob dieses Thema von Tester am
                    • HomoranH Homoran verschob dieses Thema von ...nicht in offiziellem Repo am
                    • V
                      V
                      Voodoo2man
                      schrieb zuletzt editiert von
                      #201

                      Da dieser Adapter nicht weiter gepflegt wird, habe ich einen neuen entwickelt.
                      Webuntis2 ist bereits im Latest und wartet auf Tester und RĂŒckmeldungen. 😀

                      iobroker Forum: https://forum.iobroker.net/topic/85604/test-adapter-webuntis2-version-0.2.x
                      Github: https://github.com/Voodoo2man/ioBroker.webuntis2

                      1 Antwort Letzte Antwort
                      0

                      Hey! Du scheinst an dieser Unterhaltung interessiert zu sein, hast aber noch kein Konto.

                      Hast du es satt, bei jedem Besuch durch die gleichen BeitrĂ€ge zu scrollen? Wenn du dich fĂŒr ein Konto anmeldest, kommst du immer genau dorthin zurĂŒck, wo du zuvor warst, und kannst dich ĂŒber neue Antworten benachrichtigen lassen (entweder per E-Mail oder Push-Benachrichtigung). Du kannst auch Lesezeichen speichern und BeitrĂ€ge positiv bewerten, um anderen Community-Mitgliedern deine WertschĂ€tzung zu zeigen.

                      Mit deinem Input könnte dieser Beitrag noch besser werden 💗

                      Registrieren Anmelden
                      Antworten
                      • In einem neuen Thema antworten
                      Anmelden zum Antworten
                      • Älteste zuerst
                      • Neuste zuerst
                      • Meiste Stimmen


                      Support us

                      ioBroker
                      Community Adapters
                      Donate

                      269

                      Online

                      33.1k

                      Benutzer

                      84.1k

                      Themen

                      1.4m

                      BeitrÀge
                      Community
                      Impressum | Datenschutz-Bestimmungen | Nutzungsbedingungen | Einwilligungseinstellungen
                      ioBroker Community 2014-2026
                      logo
                      • Anmelden

                      • Du hast noch kein Konto? Registrieren

                      • Anmelden oder registrieren, um zu suchen
                      • Erster Beitrag
                        Letzter Beitrag
                      0
                      • Home
                      • Aktuell
                      • Ungelesen 0
                      • Kategorien
                      • Unreplied
                      • Beliebt
                      • GitHub
                      • Docu
                      • Hilfe