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