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();
-