Skip to content

Instantly share code, notes, and snippets.

@chrisonntag
Last active October 25, 2021 20:05
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save chrisonntag/645d782bd9e48a71c6c2b84b21cfb734 to your computer and use it in GitHub Desktop.
Save chrisonntag/645d782bd9e48a71c6c2b84b21cfb734 to your computer and use it in GitHub Desktop.
Universität Passau Lesesaalbelegung - Scriptable iOS Widget
// Variables used by Scriptable
// icon-color: deep-gray; icon-glyph: th
// ProgressCircle class is adapted from https://github.com/chaeimg/battCircle
// Licence: Christoph Sonntag (https://github.com/chrisonntag), dl-de/by-2-0
class BibOccupationWidget {
constructor() {
this.source = "https://www.zim.uni-passau.de/dienstleistungen/lesesaalbelegungen/"
this.apiUrl = "https://ampel.zim.uni-passau.de/v2";
this.abbr = {
"Zentralbibliothek": "ZB",
"Juridicum": "JUR",
"Wirtschaftswissenschaften": "WIWI",
"Nikolakloster": "NK",
"Informatik/Mathematik": "IM"
}
this.abbrReverse = {
"ZB": "Zentralbibliothek",
"JUR": "Juridicum",
"WIWI": "Wirtschaftswissenschaften",
"NK": "Nikolakloster",
"IM": "Informatik/Mathematik"
}
this.shorts = Object.keys(this.abbrReverse)
this.favourite = args.widgetParameter || this.shorts[Math.floor(Math.random() * this.shorts.length)]
this.bgColor = "FFFFFF"
this.fontColor = "121212"
}
async run() {
let widget = await this.createWidget()
if (!config.runsInWidget) {
await widget.presentSmall()
}
widget.url = this.source
widget.refreshAfterDate = new Date(Date.now() + 1000 * 60 * 10) // in ms: 1000*60*10 == 10min
Script.setWidget(widget)
Script.complete()
}
async createWidget(items) {
let data = await this.getData()
// Basic widget setup
let list = new ListWidget()
list.backgroundColor = new Color(this.bgColor)
list.setPadding(0, 0, 0, 0)
let textStack = list.addStack()
textStack.setPadding(14, 14, 14, 14)
textStack.layoutVertically()
textStack.topAlignContent()
if (data.error) {
// Error handling
let loadingIndicator = textStack.addText(data.error.toUpperCase())
loadingIndicator.font = Font.mediumSystemFont(13)
loadingIndicator.textColor = new Color(this.fontColor)
loadingIndicator.textOpacity = 0.5
let spacer = textStack.addStack()
spacer.addSpacer();
} else {
// Extract data for the favourite room
let favouriteRoom = data[this.abbrReverse[this.favourite]]
let favouriteRoomName = textStack.addText(this.abbr[favouriteRoom.roomname])
favouriteRoomName.font = Font.mediumSystemFont(15)
favouriteRoomName.textColor = new Color(this.fontColor)
try {
// Room Name
let favouriteFreeSpace = parseInt(favouriteRoom.freeSpace.slice(0, -1))
let favouriteUsedSpace = 100 - favouriteFreeSpace
// Used Seats in percent
let favouriteDataStack = textStack.addStack()
favouriteDataStack.layoutHorizontally()
favouriteDataStack.bottomAlignContent()
let favouriteUsedSpaceLabel = favouriteDataStack.addText(favouriteUsedSpace + "%")
favouriteUsedSpaceLabel.font = Font.boldSystemFont(30)
favouriteUsedSpaceLabel.textColor = favouriteRoom.state == 'red' ? Color.red() : favouriteRoom.state == 'yellow' ? Color.yellow() : Color.green();
// Used seats in total
let seatsLabel = textStack.addText(Math.floor(favouriteUsedSpace / 100 * favouriteRoom.seats) + "/" + favouriteRoom.seats + " Plätze")
seatsLabel.font = Font.mediumSystemFont(12)
seatsLabel.textColor = new Color(this.fontColor)
let favouriteQualifierLabel = textStack.addText("belegt")
favouriteQualifierLabel.font = Font.regularSystemFont(10)
favouriteQualifierLabel.textColor = new Color(this.fontColor)
} catch (e) {
console.log(e)
}
delete data[this.abbrReverse[this.favourite]]
textStack.addSpacer()
let tableStack = textStack.addStack()
tableStack.layoutHorizontally()
var count = 1
for (const room in data) {
let roomData = data[room]
let dataStack = tableStack.addStack()
dataStack.layoutVertically()
dataStack.centerAlignContent()
// Remaning rooms
try {
let freeSpace = parseInt(roomData.freeSpace.slice(0, -1))
let usedSpace = 100 - freeSpace
// Progress Circle
let progressCircle = new ProgressCircle(usedSpace, this.bgColor)
let progressStack = dataStack.addStack()
progressStack.layoutVertically()
progressStack.centerAlignContent()
progressStack.setPadding(0, 0, 0, 0)
let progressCircleImage = progressStack.addImage(progressCircle.getImage())
progressCircleImage.applyFittingContentMode()
// Room name
let usedSpaceStack = dataStack.addStack()
usedSpaceStack.layoutHorizontally()
usedSpaceStack.size = new Size(24, 10)
usedSpaceStack.setPadding(2, 0, 0, 0)
let usedSpaceLabel = usedSpaceStack.addText(this.abbr[roomData.roomname])
usedSpaceLabel.centerAlignText()
usedSpaceLabel.font = Font.mediumSystemFont(8)
usedSpaceLabel.textColor = new Color(this.fontColor)
} catch (e) {
console.log("Can't parse int.")
console.log(e)
}
if (count < Object.keys(data).length) {
// add Spacer except for the last element
tableStack.addSpacer()
}
count = count + 1
}
}
return list
}
async getData() {
try {
let zimDoc = await new Request(this.apiUrl).loadString()
let webView = new WebView()
await webView.loadURL(this.apiUrl)
var rooms = `
rooms = {}
document.querySelectorAll(".room").forEach((room) => {
let roomname = room.querySelector(".roomname").innerText;
let freeSpace = room.querySelector(".detail").querySelector("tbody").querySelector("tr").querySelectorAll("td")[0].innerText;
let seats = room.querySelector(".detail").querySelector("tbody").querySelector("tr").querySelectorAll("td")[1].innerText;
let state = ""
room.querySelector(".trafficlightbody").childNodes.forEach((light) => {
let className = light.className ? light.className : ""
if (className != "" && className.slice(-6, className.length) == "active") {
state = className.slice(0,-6)
}
});
roomData = {
roomname: roomname,
freeSpace: freeSpace,
seats: seats,
state: state
}
rooms[roomData.roomname] = roomData
});
rooms
`
let response = await webView.evaluateJavaScript(rooms, false)
return response;
return {error: "Lesesaal nicht verfügbar."}
} catch (e) {
return {error: "Fehler bei Datenabruf."};
}
}
}
class ProgressCircle {
constructor(progress, bgColor) {
this.canvas = new DrawContext();
this.canvSize = 110;
this.canvTextSize = 32;
this.canvFillColor = '121212'; //remaining color
this.canvStrokeColor = 'EDEDED'; //used space color
this.canvBackColor = bgColor; //Widget background color
this.canvTextColor = '121212'; //Widget text color (use same color as above to hide text)
this.canvWidth = 11; // circle width
this.canvRadius = 50; // circle radius
this.canvas.size = new Size(this.canvSize, this.canvSize);
this.canvas.respectScreenScale = true;
this.progress = progress;
this.drawArc(
new Point(this.canvSize / 2, this.canvSize / 2),
this.canvRadius,
this.canvWidth,
Math.floor(this.progress * 3.6)
);
this.canvTextRect = new Rect(
0,
100 - this.canvTextSize * 2,
this.canvSize,
this.canvTextSize
);
this.canvas.setTextAlignedCenter();
this.canvas.setTextColor(new Color(this.canvTextColor));
this.canvas.setFont(Font.mediumSystemFont(this.canvTextSize));
this.canvas.drawTextInRect("" + this.progress + "%", this.canvTextRect);
}
getImage() {
return this.canvas.getImage();
}
sinDeg(deg) {
return Math.sin((deg * Math.PI) / 180);
}
cosDeg(deg) {
return Math.cos((deg * Math.PI) / 180);
}
drawArc(ctr, rad, w, deg) {
let bgx = ctr.x - rad;
let bgy = ctr.y - rad;
let bgd = 2 * rad;
let bgr = new Rect(bgx, bgy, bgd, bgd);
let bgc = new Rect(0, 0, this.canvSize, this.canvSize);
this.canvas.setFillColor(new Color(this.canvBackColor));
this.canvas.fill(bgc);
this.canvas.setFillColor(new Color(this.canvFillColor));
this.canvas.setStrokeColor(new Color(this.canvStrokeColor));
this.canvas.setLineWidth(w);
this.canvas.strokeEllipse(bgr);
for (let t = 0; t < deg; t++) {
let rect_x = ctr.x + rad * this.sinDeg(t) - w / 2;
let rect_y = ctr.y - rad * this.cosDeg(t) - w / 2;
let rect_r = new Rect(rect_x, rect_y, w, w);
this.canvas.fillEllipse(rect_r);
}
}
}
await new BibOccupationWidget().run();
@chrisonntag
Copy link
Author

chrisonntag commented Oct 25, 2021

Lesesaalbelegung - Universität Passau

Auf Basis der Daten der Uni Passau werden die aktuell belegten Plätze in allen verfügbaren Bibliotheken angezeigt.

Installation

  1. Die Scriptable App herunterladen
  2. Den kompletten Code von oben in die Zwischenablage kopieren
  3. Scriptable öffnen und mit Klicken auf das + in der oberen rechten Ecke ein neues Skript hinzufügen
  4. Den eben kopierten Code einfügen und das Dokument durch Klicken auf den Titel ganz oben umbenennen
  5. Auf den Homescreen wechseln und durch langes Drücken auf eine freie Fläche und durch das + Symbol in der linken oberen Ecke ein neues Widget hinzufügen
  6. Lange auf das Widget drücken, das erstellte Skript auswählen
  7. Das Kürzel der Lieblings-Bib eintragen (IM, WIKI, JUR, ZB oder NK). Wird nichts spezielles eingetragen, wird für die große Anzeige immer eine Zufalls-Bibliothek ausgewählt.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment