commit 1466bf2c8eb30fb46ad0c0c810550589b0feed94 Author: apoc Date: Thu Feb 11 01:00:47 2021 +0100 initial commit diff --git a/chaospott_mumble.js b/chaospott_mumble.js new file mode 100644 index 0000000..be6546d --- /dev/null +++ b/chaospott_mumble.js @@ -0,0 +1,229 @@ +// Variables used by Scriptable. +// These must be at the very top of the file. Do not edit. +// icon-color: yellow; icon-glyph: magic; +const mumbleApiUrl = "https://apoc.uber.space/chaospott_mumble.json"; +const spaceApiUrl = "https://status.chaospott.de/status.json"; +const logoUrl = "https://chaospott.de/images/logo.png"; +const logoLocalFilename = "chaospott_logo.png"; +const mumbleLocalFilename = "chaospott_mumble.json"; +const spaceLocalFilename = "chaospott_space.json"; + +const title = "Chaospott"; +const subTitle = "Essen"; + +var colorSpaceClosed; +var colorSpaceOpen; + +var widget = await createWidget(); + +if (!config.runsInWidget) { + await widget.presentSmall(); +} +Script.setWidget(widget); +Script.complete(); + + +async function createWidget(){ + 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); + var colorBorderOpen = colorOpenFresh; + var colorBorderClosed = colorClosedFresh; + var colorBorderLonely = colorLonelyFresh; + var colorMumbleOpen; + var colorMumbleClosed; + var colorMumbleLonely; + + const widget = new ListWidget(); + + try { + var [mumbleStatus, mumbleFresh] = await getJSONandCache(mumbleLocalFilename, mumbleApiUrl); + var [spaceStatus, spaceFresh] = await getJSONandCache(spaceLocalFilename, spaceApiUrl); + } catch(err) { + const errorList = new ListWidget(); + errorList.addText("Please enable internet for initial execution."); + return errorList; + } + + if (mumbleFresh){ + colorMumbleOpen = colorOpenFresh; + colorMumbleClosed = colorClosedFresh; + colorMumbleLonely = colorLonelyFresh; + } else { + colorMumbleOpen = colorOpenStale; + colorMumbleClosed = colorClosedStale; + colorMumbleLonely = colorLonelyStale; + colorBorderOpen = colorOpenStale; + colorBorderClosed = colorClosedStale; + colorBorderLonely = colorLonelyStale; + } + + if (spaceFresh){ + colorSpaceOpen = colorOpenFresh; + colorSpaceClosed = colorClosedFresh; + } else { + colorSpaceOpen = colorOpenStale; + colorSpaceClosed = colorClosedStale; + colorBorderOpen = colorOpenStale; + colorBorderClosed = colorClosedStale; + colorBorderLonely = colorLonelyStale; + } + + 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; + } + } + + widget.setPadding(0, 5, 0, 5); + canvasStack = widget.addStack(); + canvasStack.setPadding(5, 15, 5, 15); + canvasStack.cornerRadius = 15; + canvasStack.layoutVertically(); + canvasStack.backgroundColor = Color.dynamic(Color.white(), Color.black()); + + const headerStack = canvasStack.addStack(); + const titleStack = headerStack.addStack(); + + titleStack.layoutVertically(); + const titleText = titleStack.addText(title); + titleText.font = Font.regularSystemFont(16); + + const subTitleText = titleStack.addText(subTitle); + subTitleText.font = Font.mediumSystemFont(10); + + headerStack.addSpacer(5); + + let logo = await getCachedImage(logoLocalFilename, logoUrl); + const logoImage = headerStack.addImage(logo); + logoImage.imageSize = new Size(30, 30); + + canvasStack.addSpacer(5); + + const middleRow = canvasStack.addStack(); + spaceStatus.sensors.door_locked.forEach((obj,i,arr) => { + spaceView(middleRow, obj.location, obj.value); + if(i !== arr.length - 1){middleRow.addSpacer();} + }); + + const spaceLastUpdate = new Date(spaceStatus.state.lastchange*1000); + const spaceLastUpdateStack = canvasStack.addStack(); + let spaceLastUpdateLabel = spaceLastUpdateStack.addDate(spaceLastUpdate); + spaceLastUpdateLabel.font = Font.mediumSystemFont(6); + spaceLastUpdateLabel.applyRelativeStyle(); + + const bottomRow = canvasStack.addStack(); + bottomRow.useDefaultPadding(); + bottomRow.centerAlignContent(); + const mumbleLabelStack = bottomRow.addStack(); + mumbleLabelStack.layoutVertically(); + const labelMumble = mumbleLabelStack.addText("Mumble"); + labelMumble.font = Font.regularSystemFont(14); + + let mumbleLastUpdate = new Date(mumbleStatus.last_update * 1000 ); + const labelMumbleUpdated = mumbleLabelStack.addDate(mumbleLastUpdate); + labelMumbleUpdated.font = Font.mediumSystemFont(6); + labelMumbleUpdated.applyTimeStyle(); + + bottomRow.addSpacer(23); + const mumbleValueStack = bottomRow.addStack(); + const labelMumbleUser = mumbleValueStack.addText(mumbleStatus.connected_users.toString(10)); + labelMumbleUser.font = Font.boldSystemFont(30); + switch(mumbleStatus.connected_users){ + case 0: + labelMumbleUser.textColor = colorMumbleClosed; + break; + case 1: + labelMumbleUser.textColor = colorMumbleLonely; + break; + default: + labelMumbleUser.textColor = colorMumbleOpen; + } + + canvasStack.addSpacer(4) + + dateStack = canvasStack.addStack(); + dateStack.layoutHorizontally(); + dateStack.bottomAlignContent(); + dateStack.addSpacer(41); + const now = new Date(); + const timeLabel = dateStack.addDate(now) + timeLabel.font = Font.mediumSystemFont(10) + timeLabel.centerAlignText() + timeLabel.applyTimeStyle() + timeLabel.textColor = Color.darkGray() + + return widget; +} + +function spaceView(widget, space, lockStatus) { + const viewStack = widget.addStack(); + viewStack.layoutVertically(); + viewStack.centerAlignContent(); + + const spaceName = space.charAt(0).toUpperCase() + space.slice(1) + const label = viewStack.addText(spaceName); + label.font = Font.regularSystemFont(14); + + const lock = SFSymbol.named("lock." + (lockStatus ? "" : "open.") + "fill"); + lock.applyFont(Font.systemFont(20)); + const lockImage = viewStack.addImage(lock.image); + lockImage.resizable = false; + lockImage.imageSize = new Size(25, 25); + if(lockStatus){ + lockImage.tintColor = colorSpaceClosed; + } else { + lockImage.tintColor = colorSpaceOpen; + } +} + +async function getCachedImage(localFilename, url) { + let fm = FileManager.local(); + let dir = fm.cacheDirectory(); + let path = fm.joinPath(dir, localFilename); + if (fm.fileExists(path)) { + return fm.readImage(path); + } else { + let r = new Request(url); + try { + let returnImage = await r.loadImage(); + fm.writeImage(path, returnImage); + return returnImage; + } catch (err) { + // return placeholder + return SFSymbol.named("photo").image; + } + } +} + +async function getJSONandCache(localFilename, url){ + let fm = FileManager.local() + let dir = fm.cacheDirectory() + let path = fm.joinPath(dir, localFilename) + let r = new Request(url) + try { + var data = await r.loadJSON() + fm.writeString(path, JSON.stringify(data, null, 2)) + var fresh = true; + } catch (err) { + if (fm.fileExists(path)) { + data = JSON.parse(fm.readString(path), null) + fresh = false; + } else { + throw "no data"; + } + } + return [data, fresh]; +} diff --git a/chaospott_mumble.py b/chaospott_mumble.py new file mode 100755 index 0000000..d6c07a2 --- /dev/null +++ b/chaospott_mumble.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python +# -*- coding: utf-8 +from struct import * +import socket, time, datetime, sys, json + +try: + host = "mumble.chaospott.de" + port = 64738 + + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + s.settimeout(1) + + buf = pack(">iQ", 0, datetime.datetime.now().microsecond) + s.sendto(buf, (host, port)) + + try: + data, addr = s.recvfrom(1024) + except socket.timeout: + print("%d:NaN:NaN" % (time.time())) + sys.exit() + + r = unpack(">bbbbQiii", data) + + version = "%d.%d.%d" % (r[1], r[2], r[3]) + last_update = int(time.time()) + output = { "server_version": version, "connected_users": r[5], "max_users": r[6], "bandwidth": r[7], "last_update": last_update} + print(json.dumps(output)) +except: + pass