NEWS
UNIFI API Voucher Skript
-
Hallo zusammen,
ich habe mal GEMINI bemüht mir ein Voucher Skript für die Unifi API zu bauen, das in IOBROKER läuft. Vielleicht kanns wer brauchen.
Funktionen deines WLAN-Voucher-Generierungs-Skripts
Dieses ioBroker-Skript ist dein persönlicher Assistent zur automatischen Erstellung und Verwaltung von WLAN-Vouchern für dein UniFi-Netzwerk, direkt aus deiner ioBroker-Steuerungsoberfläche (VIS) heraus.
Was das Skript macht:
-
Vorbereitung und Einrichtung:
- Beim Start sorgt das Skript dafür, dass alle notwendigen "Schalter" und "Anzeigefelder" in ioBroker existieren. Diese findest du unter
0_userdata.0.WIFIVoucher. Dazu gehören Felder, um einzustellen, wie viele Nutzer einen Voucher verwenden dürfen (max_users) und wie lange er gültig ist (valid_days), sowie ein "Start"-Knopf (generate) und ein Feld, das dir den fertigen Code anzeigt (actual_voucher).
- Beim Start sorgt das Skript dafür, dass alle notwendigen "Schalter" und "Anzeigefelder" in ioBroker existieren. Diese findest du unter
-
WLAN-Voucher generieren:
- Wenn du in deiner ioBroker-Ansicht den "Start"-Knopf (
0_userdata.0.WIFIVoucher.generate) betätigst, tritt das Skript in Aktion. - Es liest deine zuvor eingestellten Werte für die maximale Nutzerzahl und die Gültigkeitsdauer aus den entsprechenden Feldern.
- Mit diesen Informationen sendet das Skript eine verschlüsselte und authentifizierte Anfrage an deine UniFi-Hardware (den UniFi Controller, z.B. deine USG Max). Dies geschieht direkt und sicher, ohne Umwege über externe Programme wie
curl. - Deine UniFi-Hardware erstellt daraufhin einen neuen WLAN-Voucher.
- Wenn du in deiner ioBroker-Ansicht den "Start"-Knopf (
-
Voucher-Code anzeigen und formatieren:
- Sobald der neue Voucher von UniFi generiert wurde, empfängt das Skript den Voucher-Code.
- Es formatiert diesen Code sofort, sodass er leichter zu lesen ist – beispielsweise wird aus "1234567890" ein "12345-67890".
- Der so formatierte Code wird dann in deinem ioBroker-Anzeigefeld (
0_userdata.0.WIFIVoucher.actual_voucher) dargestellt, sodass du ihn direkt ablesen und weitergeben kannst.
-
Automatische Code-Anzeige und Zurücksetzung:
- Nachdem der Voucher-Code angezeigt wurde, startet das Skript einen 5-minütigen Countdown.
- Während dieser Zeit bleibt der Code sichtbar. Wenn die 5 Minuten abgelaufen sind, wird der angezeigte Voucher-Code automatisch durch eine Reihe von Nullen ("00000-00000") ersetzt. Das sorgt dafür, dass nicht dauerhaft alte Codes sichtbar bleiben und du immer den aktuellen Code im Blick hast.
- Wichtig: Solltest du einen neuen Voucher generieren, während der alte Code noch angezeigt wird, wird der Countdown sofort zurückgesetzt und ein neuer 5-Minuten-Zeitraum für den neuen Code gestartet. So wird sichergestellt, dass immer der aktuellste Code für 5 Minuten sichtbar ist.
-
Bereitschaft für den nächsten Voucher:
- Nachdem der Generierungsprozess abgeschlossen ist (egal ob erfolgreich oder mit einem Problem), setzt das Skript den "Start"-Knopf (
0_userdata.0.WIFIVoucher.generate) in ioBroker automatisch wieder auf seine Ausgangsposition zurück. So ist er bereit für die nächste Voucher-Erstellung.
- Nachdem der Generierungsprozess abgeschlossen ist (egal ob erfolgreich oder mit einem Problem), setzt das Skript den "Start"-Knopf (
// Direkte Nutzung des HTTPS-Moduls für API-Anfragen const https = require('https'); // --- Konfiguration --- // API-Endpunkt für deine UniFi USG MAX und spezifische Site-ID const API_URL_BASE = "https://192.168.xxx.xxx/proxy/network/integration/v1/sites/*site-id*"; // Dein UniFi API-Schlüssel const API_KEY = "*API Token*"; // Fester Name für automatisch generierte Voucher im UniFi Controller const VOUCHER_NAME = "autogen"; // --- Datenpunkte definieren --- const DP_BASE = "0_userdata.0.WIFIVoucher"; const DP_GENERATE = DP_BASE + ".generate"; // Boolean: TRUE zum Generieren eines Vouchers const DP_ACTUAL_VOUCHER = DP_BASE + ".actual_voucher"; // String: Speichert den generierten Voucher-Code const DP_MAX_USERS = DP_BASE + ".max_users"; // Number: Maximale Nutzer pro Voucher const DP_VALID_DAYS = DP_BASE + ".valid_days"; // Number: Gültigkeitsdauer in Tagen // --- Timer-Variable für das automatische Überschreiben --- let voucherTimeout = null; // --- Funktion zum Erstellen oder Aktualisieren von Datenpunkten --- async function createDataPoints() { log("Prüfe und erstelle Datenpunkte...", "info"); // Datenpunkt zum Triggern der Voucher-Generierung await createStateAsync(DP_GENERATE, false, { name: "WLAN Voucher generieren", desc: "Setzen Sie auf TRUE, um einen neuen WLAN Voucher zu generieren.", type: "boolean", role: "button", read: true, write: true }); // Datenpunkt zum Speichern des generierten Voucher-Codes await createStateAsync(DP_ACTUAL_VOUCHER, "", { name: "Aktueller WLAN Voucher Code", desc: "Der zuletzt generierte WLAN Voucher Code.", type: "string", role: "value", read: true, write: false // Nur lesbar durch VIS, geschrieben vom Skript }); // Datenpunkt für die maximale Anzahl der Nutzer pro Voucher await createStateAsync(DP_MAX_USERS, 1, { // Standardwert: 1 Nutzer name: "Max. Nutzer pro Voucher", desc: "Die maximale Anzahl von Geräten, die den Voucher nutzen können (UniFi-Max: 10).", type: "number", role: "value", read: true, write: true, min: 1, max: 10 // Übliches Maximum in UniFi für Guest Limit }); // Datenpunkt für die Gültigkeitsdauer in Tagen // Beachte: UniFi API hat ein internes Limit für timeLimitMinutes (z.B. 43200 Stunden = 1800 Tage). // Ein höherer Wert wird vom Controller wahrscheinlich auf sein internes Maximum begrenzt. await createStateAsync(DP_VALID_DAYS, 1, { // Standardwert: 1 Tag name: "Gültigkeit des Vouchers (Tage)", desc: "Die Gültigkeitsdauer des Vouchers in Tagen (UniFi-Limit beachten).", type: "number", role: "value", read: true, write: true, min: 1, max: 128000 // Maximalwert laut deiner Anforderung }); log("Datenpunkte geprüft/erstellt.", "info"); } // --- Funktion zum Formatieren des Voucher-Codes (5 Ziffern - 5 Ziffern) --- function formatVoucherCode(code) { if (typeof code !== 'string' || code.length !== 10) { return code; // Gib den Code unverändert zurück, wenn er nicht 10 Ziffern lang ist } return code.substring(0, 5) + '-' + code.substring(5, 10); } // --- Funktion zum Zurücksetzen des Voucher-Codes nach Timeout --- function resetVoucherCode() { log("Setze Voucher-Code auf '00000-00000' zurück.", "info"); setState(DP_ACTUAL_VOUCHER, "00000-00000", true); // Bestätigen, dass der Wert geschrieben wurde } // --- Hauptfunktion zum Generieren eines einzelnen Vouchers --- async function generateVoucher() { log("Starte Voucher-Generierung...", "info"); // **WICHTIG:** Alten Timer stoppen, falls vorhanden if (voucherTimeout) { clearTimeout(voucherTimeout); log("Vorherigen Voucher-Timeout gestoppt.", "debug"); } // Lese die aktuellen Werte aus den Datenpunkten const maxUsersState = await getStateAsync(DP_MAX_USERS); const validDaysState = await getStateAsync(DP_VALID_DAYS); // Prüfe, ob die Datenpunkte gültige Werte liefern if (!maxUsersState || maxUsersState.val === null || !validDaysState || validDaysState.val === null) { log("Fehler: Datenpunkte für max. Nutzer oder Gültigkeit konnten nicht gelesen werden.", "warn"); setState(DP_GENERATE, false); // Setze Schalter zurück, da keine gültigen Daten return; } const maxUsers = maxUsersState.val; const validDays = validDaysState.val; // Umrechnung von Tagen in Minuten, wie von der UniFi API erwartet const timeLimitMinutes = validDays * 24 * 60; // Erstelle das JSON-Objekt für den POST-Request-Body const postDataObj = { name: VOUCHER_NAME, authorizedGuestLimit: maxUsers, timeLimitMinutes: timeLimitMinutes }; const postData = JSON.stringify(postDataObj); // Konvertiere Objekt in JSON-String // Manuelle URL-Zerlegung für das https-Modul const fullUrl = API_URL_BASE + '/hotspot/vouchers'; const parts = fullUrl.match(/^https?:\/\/([^/:]+)(:\d+)?(.*)$/); if (!parts) { log(`Fehler: Ungültige URL-Formatierung für ${fullUrl}. Bitte URL prüfen.`, "error"); setState(DP_GENERATE, false); return; } const hostname = parts[1]; const port = parts[2] ? parseInt(parts[2].substring(1)) : 443; // Standard-HTTPS-Port 443 const path = parts[3]; // Optionen für den HTTPS-Request const options = { hostname: hostname, port: port, path: path, method: 'POST', headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(postData), // Notwendig für POST-Requests 'Accept': 'application/json', 'X-API-Key': API_KEY // Dein API-Schlüssel zur Authentifizierung }, rejectUnauthorized: false // Wichtig: Deaktiviert die Zertifikatsprüfung für selbstsignierte UniFi-Zertifikate }; log(`Sende HTTP POST-Request an: ${fullUrl}`, "debug"); log(`Post-Daten: ${postData}`, "debug"); try { const responseData = await new Promise((resolve, reject) => { const req = https.request(options, (res) => { let data = ''; res.on('data', (chunk) => { data += chunk; }); res.on('end', () => { if (res.statusCode >= 200 && res.statusCode < 300) { resolve(data); } else { reject(new Error(`Server responded with status code ${res.statusCode}: ${data}`)); } }); }); req.on('error', (e) => { reject(e); }); req.write(postData); req.end(); }); // Verarbeitung der erfolgreichen API-Antwort log("API-Antwort erhalten: " + responseData, "debug"); const result = JSON.parse(responseData); if (result && Array.isArray(result.vouchers) && result.vouchers.length > 0) { const rawVoucherCode = result.vouchers[0].code; const formattedVoucherCode = formatVoucherCode(rawVoucherCode); // Voucher-Code formatieren log(`Neuer Voucher erfolgreich generiert: ${formattedVoucherCode}`, "info"); // Speichere den formatierten Code im ioBroker Datenpunkt setState(DP_ACTUAL_VOUCHER, formattedVoucherCode, true); // Bestätigen, dass der Wert geschrieben wurde // **Neuer Timer starten** voucherTimeout = setTimeout(resetVoucherCode, 5 * 60 * 1000); // 5 Minuten = 5 * 60 * 1000 ms log("Timeout für Voucher-Rücksetzung gestartet (5 Minuten).", "debug"); } else { log("Unerwartete API-Antwort Struktur bei Voucher-Generierung: " + responseData, "warn"); if (result && result.statusCode) { log(`API Fehler-Code: ${result.statusCode}, Nachricht: ${result.message || 'Keine Nachricht'}`, "error"); } } } catch (e) { log(`Ausnahme beim Generieren des Vouchers: ${e.message}. Stack: ${e.stack}`, "error"); } finally { setState(DP_GENERATE, false, true); // Setzt den Trigger-Datenpunkt immer auf FALSE zurück } } // --- Skriptstart und Trigger-Definitionen --- // Dieser Listener sorgt dafür, dass die Datenpunkte initialisiert werden, // wenn das Skript aktiviert oder neu gestartet wird. on({ id: "javascript.0.scriptEnabled.script.js." + instance + ".WLAN_Voucher_Generator", change: "ne" }, async function (obj) { if (obj.state.val === true) { log("Skript gestartet. Datenpunkte werden initialisiert.", "info"); await createDataPoints(); } }); // Dieser Trigger reagiert auf Änderungen des "generate"-Datenpunkts. // Wenn dieser auf TRUE gesetzt wird, startet die Voucher-Generierung. on({ id: DP_GENERATE, change: "ne", val: true }, async function (obj) { log("Datenpunkt " + DP_GENERATE + " auf TRUE gesetzt. Starte Voucher-Generierung...", "info"); await generateVoucher(); }); // Initialer Aufruf zum Erstellen der Datenpunkte beim Skriptstart createDataPoints(); -
-
Ein gutes Skript und genau das was ich suchte. Jedoch hatte Ich Schwierigkeiten mein site-ID herauszufinden und daher funktionierte das Skript nicht.
Ich habe dann ein kleines Blockly geschrieben und dann folgendes in den auszuführenden Befehl geschrieben: curl -k -X GET "https://192.168.x.y/proxy/network/integration/v1/sites" -H "X-API-KEY: API Token" -H 'Accept: application/json'

Zurück kommt dann im Log ein String: {...[{"id":"site-id",...]}
Jetzt tut es das was es soll.Gruß aus dem Norden und danke für's Posten
Ps: Zeile 194 kann die Verschwindezeit geändert werden und in Zeile 86 kann auch ein " " eingegeben werden um alle auszublenden.
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