// Variables used by Scriptable. // These must be at the very top of the FileManager. Do not edit. // icon-color: deep-green; icon-glyph: power-off; // API-URLs für Mumble- und Space-Status const mumbleApiUrl = "https://status.chaospott.de/chaospott_mumble.json"; const spaceApiUrl = "https://status.chaospott.de/status.json"; // URLs für das Logo und lokale Dateinamen für Caching const logoUrl = "https://chaospott.de/images/logo.png"; const logoLocalFilename = "chaospott_logo.png"; const mumbleLocalFilename = "chaospott_mumble.json"; const spaceLocalFilename = "chaospott_space.json"; // Titel und Untertitel des Widgets const title = "Chaospott"; const subTitle = "Essen"; // Variablen für die Farbzustände der Räume var colorSpaceClosed; var colorSpaceOpen; // Widget-Erstellung und Anzeige var widget = await createWidget(); // Wenn das Skript nicht im Widget-Kontext läuft, präsentiere das Widget als kleines Fenster if (!config.runsInWidget) { await widget.presentSmall(); } // Setze das Widget Script.setWidget(widget); Script.complete(); // Funktion zur Erstellung des Widgets async function createWidget() { // Definiere Farben für verschiedene Status const colorOpenFresh = Color.green(); const colorOpenStale = new Color("#00ff00", 0.3); const colorClosedFresh = new Color("#ff0000", 1.0); const colorClosedStale = new Color("#ff0000", 0.3); const colorLonelyFresh = new Color("#ff8800", 1.0); const colorLonelyStale = new Color("#ff8800", 0.3); // Setze Standardfarben für den Widget-Hintergrund var colorBorderOpen = colorOpenFresh; var colorBorderClosed = colorClosedFresh; var colorBorderLonely = colorLonelyFresh; var colorMumbleOpen; var colorMumbleClosed; var colorMumbleLonely; // Erstelle ein neues Widget const widget = new ListWidget(); // Versuche, die JSON-Daten von den APIs abzurufen und im Cache zu speichern try { var [mumbleStatus, mumbleFresh] = await getJSONandCache(mumbleLocalFilename, mumbleApiUrl); var [spaceStatus, spaceFresh] = await getJSONandCache(spaceLocalFilename, spaceApiUrl); } catch (err) { // Bei Fehlern zeige eine Fehlermeldung an const errorList = new ListWidget(); errorList.addText("Please enable internet for initial execution."); return errorList; } // Setze die Farben basierend auf den aktuellen Mumble-Daten if (mumbleFresh) { colorMumbleOpen = colorOpenFresh; colorMumbleClosed = colorClosedFresh; colorMumbleLonely = colorLonelyFresh; } else { colorMumbleOpen = colorOpenStale; colorMumbleClosed = colorClosedStale; colorMumbleLonely = colorLonelyStale; colorBorderOpen = colorOpenStale; colorBorderClosed = colorClosedStale; colorBorderLonely = colorLonelyStale; } // Setze die Farben basierend auf den aktuellen Space-Daten if (spaceFresh) { colorSpaceOpen = colorOpenFresh; colorSpaceClosed = colorClosedFresh; } else { colorSpaceOpen = colorOpenStale; colorSpaceClosed = colorClosedStale; colorBorderOpen = colorOpenStale; colorBorderClosed = colorClosedStale; colorBorderLonely = colorLonelyStale; } // Bestimme die Hintergrundfarbe des Widgets basierend auf dem Status des Raums und der Benutzeranzahl if (spaceStatus.state.open) { widget.backgroundColor = colorBorderOpen; } else { switch (mumbleStatus.connected_users) { case 0: widget.backgroundColor = colorBorderClosed; break; case 1: widget.backgroundColor = colorBorderLonely; break; default: widget.backgroundColor = colorBorderOpen; } } // Setze Padding für das Widget widget.setPadding(0, 5, 0, 5); canvasStack = widget.addStack(); canvasStack.setPadding(8, 15, 8, 15); canvasStack.cornerRadius = 15; // Runde die Ecken des Widgets canvasStack.layoutVertically(); // Vertikale Anordnung der Elemente canvasStack.backgroundColor = Color.dynamic(Color.white(), Color.black()); // Header-Stack für Titel und Logo const headerStack = canvasStack.addStack(); const titleStack = headerStack.addStack(); titleStack.layoutVertically(); // Vertikale Anordnung für Titel und Untertitel const titleText = titleStack.addText(title); // Titel hinzufügen titleText.font = Font.regularSystemFont(16); // Schriftart für Titel const subTitleText = titleStack.addText(subTitle); // Untertitel hinzufügen subTitleText.font = Font.mediumSystemFont(10); // Schriftart für Untertitel headerStack.addSpacer(5); // Abstand hinzufügen // Logo hinzufügen let logo = await getCachedImage(logoLocalFilename, logoUrl); const logoImage = headerStack.addImage(logo); logoImage.imageSize = new Size(30, 30); // Größe des Logos canvasStack.addSpacer(5); // Abstand hinzufügen // Mittelreihe für den Raumstatus const middleRow = canvasStack.addStack(); spaceStatus.sensors.door_locked.forEach((obj, i, arr) => { spaceView(middleRow, obj.location, obj.value); // Raumansicht für jeden Sensor if (i !== arr.length - 1) { middleRow.addSpacer(); } // Abstand zwischen den Sensoren }); // Letzte Aktualisierung des Raums anzeigen const spaceLastUpdate = new Date(spaceStatus.state.lastchange * 1000); const spaceLastUpdateStack = canvasStack.addStack(); let spaceLastUpdateLabel = spaceLastUpdateStack.addDate(spaceLastUpdate); spaceLastUpdateLabel.font = Font.mediumSystemFont(6); // Schriftart für das Datum spaceLastUpdateLabel.applyRelativeStyle(); // Relatives Datum anwenden // Untere Reihe für Mumble-Status const bottomRow = canvasStack.addStack(); bottomRow.useDefaultPadding(); // Standard-Padding verwenden bottomRow.centerAlignContent(); // Inhalte zentrieren const mumbleLabelStack = bottomRow.addStack(); mumbleLabelStack.layoutVertically(); // Vertikale Anordnung für Mumble-Label const labelMumble = mumbleLabelStack.addText("M"); // Mumble-Label hinzufügen labelMumble.font = Font.regularSystemFont(12); // Schriftart für Mumble-Label // Letzte Aktualisierung des Mumble-Servers anzeigen let mumbleLastUpdate = new Date(mumbleStatus.last_update * 1000); const labelMumbleUpdated = mumbleLabelStack.addDate(mumbleLastUpdate); labelMumbleUpdated.font = Font.mediumSystemFont(6); // Schriftart für das Datum labelMumbleUpdated.applyTimeStyle(); // Zeitstil anwenden bottomRow.addSpacer(3); // Abstand hinzufügen // Anzahl der verbundenen Benutzer anzeigen const mumbleValueStack = bottomRow.addStack(); const labelMumbleUser = mumbleValueStack.addText(mumbleStatus.connected_users.toString(10)); // Benutzeranzahl hinzufügen labelMumbleUser.font = Font.boldSystemFont(23); // Schriftart für Benutzeranzahl // Benutzeranzahl-Farbe basierend auf dem Status festlegen switch (mumbleStatus.connected_users) { case 0: labelMumbleUser.textColor = colorMumbleClosed; // Rot für geschlossen break; case 1: labelMumbleUser.textColor = colorMumbleLonely; // Orange für einsam break; default: labelMumbleUser.textColor = colorMumbleOpen; // Grün für offen } bottomRow.addSpacer(10); // Abstand hinzufügen // Abstand zwischen den Elementen canvasStack.addSpacer(4); // Datum und Uhrzeit hinzufügen dateStack = canvasStack.addStack(); dateStack.layoutHorizontally(); // Horizontale Anordnung für Datum dateStack.bottomAlignContent(); // Inhalte am unteren Rand ausrichten dateStack.addSpacer(41); // Abstand hinzufügen const now = new Date(); // Aktuelle Zeit abrufen const timeLabel = dateStack.addDate(now); // Uhrzeit hinzufügen timeLabel.font = Font.mediumSystemFont(10); // Schriftart für die Uhrzeit timeLabel.centerAlignText(); // Uhrzeit zentrieren timeLabel.applyTimeStyle(); // Zeitstil anwenden timeLabel.textColor = Color.darkGray(); // Schriftfarbe für die Uhrzeit return widget; // Das erstellte Widget zurückgeben } // Funktion zur Anzeige des Raumstatus function spaceView(widget, space, lockStatus) { const viewStack = widget.addStack(); // Neuen Stack für den Raumstatus hinzufügen viewStack.layoutVertically(); // Vertikale Anordnung viewStack.centerAlignContent(); // Inhalte zentrieren // Raumname formatieren const spaceName = space.charAt(0).toUpperCase() + space.slice(1); const label = viewStack.addText(spaceName); // Raumname hinzufügen label.font = Font.regularSystemFont(14); // Schriftart für Raumname // Schloss-Symbol hinzufügen, abhängig vom Lock-Status const lock = SFSymbol.named("lock." + (lockStatus ? "" : "open.") + "fill"); lock.applyFont(Font.systemFont(20)); // Schriftart für das Symbol const lockImage = viewStack.addImage(lock.image); // Schlossbild hinzufügen lockImage.resizable = false; // Größe des Bildes festlegen lockImage.imageSize = new Size(25, 25); // Größe des Schlosses // Färbe das Schloss basierend auf dem Lock-Status if (lockStatus) { lockImage.tintColor = colorSpaceClosed; // Rot für geschlossen } else { lockImage.tintColor = colorSpaceOpen; // Grün für offen } } // Funktion zum Abrufen und Cachen von Bildern async function getCachedImage(localFilename, url) { let fm = FileManager.local(); // Lokalen FileManager abrufen let dir = fm.cacheDirectory(); // Cache-Verzeichnis abrufen let path = fm.joinPath(dir, localFilename); // Pfad zur lokalen Datei erstellen if (fm.fileExists(path)) { return fm.readImage(path); // Bild aus dem Cache lesen, wenn vorhanden } else { let r = new Request(url); // Anfrage an die URL erstellen try { let returnImage = await r.loadImage(); // Bild von der URL laden fm.writeImage(path, returnImage); // Bild im Cache speichern return returnImage; // Das geladene Bild zurückgeben } catch (err) { // Bei Fehlern ein Platzhalterbild zurückgeben return SFSymbol.named("photo").image; } } } // Funktion zum Abrufen und Cachen von JSON-Daten async function getJSONandCache(localFilename, url) { let fm = FileManager.local(); // Lokalen FileManager abrufen let dir = fm.cacheDirectory(); // Cache-Verzeichnis abrufen let path = fm.joinPath(dir, localFilename); // Pfad zur lokalen Datei erstellen let r = new Request(url); // Anfrage an die URL erstellen try { var data = await r.loadJSON(); // JSON-Daten von der URL laden fm.writeString(path, JSON.stringify(data, null, 2)); // Daten im Cache speichern var fresh = true; // Daten sind aktuell } catch (err) { // Wenn der Abruf fehlschlägt, versuche, die zwischengespeicherte Version zu lesen if (fm.fileExists(path)) { data = JSON.parse(fm.readString(path), null); // Daten aus dem Cache lesen fresh = false; // Daten sind nicht aktuell } else { throw "no data"; // Keine Daten vorhanden } } return [data, fresh]; // Die Daten und die Aktualität der Daten zurück }