Files
Spacestatus-Widget/SpaceStatus.js
2025-08-18 20:27:09 +02:00

274 lines
11 KiB
JavaScript

// 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
}