Skip to content

Instantly share code, notes, and snippets.

@marco79cgn
Last active January 11, 2023 21:47
Star You must be signed in to star a gist
Save marco79cgn/b5f291d6242a2c530e56c748f1ae7f2c to your computer and use it in GitHub Desktop.
A Scriptable widget that shows the amount of people who have received the corona vaccination in Germany
// Version 1.3.0
// 27.11.2021
//
// Variables used by Scriptable.
// These must be at the very top of the file. Do not edit.
// icon-color: red; icon-glyph: notes-medical;
// Mit Caching und Fallback
const cacheMinutes = 60; // 60 min
const today = new Date();
const neededTotalVaccinations = 83200000;
let result;
let resultDe;
let width = 100;
const h = 5;
const colorLightGreen = new Color("#47b881")
const colorDarkGreen = new Color("#00783e")
let widget = new ListWidget();
widget.setPadding(6, 12, 8, 12);
widget.url =
"https://interaktiv.morgenpost.de/corona-impfungen-deutschland-bundeslaender-weltweit/";
await getNumbers();
await createWidget();
Script.setWidget(widget);
Script.complete();
if (config.runsInApp) {
widget.presentSmall();
}
async function createWidget() {
widget.addSpacer();
const amountFirstPerCent = resultDe.vaccinations1_quota;
const amountSecondPerCent = resultDe.vaccinations2_quota;
const amountBoosterPerCent = resultDe.vaccinations3_quota;
let firstDoseStack = widget.addStack()
firstDoseStack.layoutHorizontally()
let amountFirstText = firstDoseStack.addText("Erstimpfung");
amountFirstText.font = Font.boldSystemFont(12);
firstDoseStack.addSpacer()
let percentFirstText = firstDoseStack.addText(amountFirstPerCent.toLocaleString() + "%")
percentFirstText.font = Font.boldSystemFont(12);
widget.addSpacer(2);
let progressStack = widget.addStack();
progressStack.layoutVertically();
let progressNumberStack = widget.addStack();
progressNumberStack.layoutHorizontally();
const progressText = progressNumberStack.addText((resultDe.vaccinations1).toLocaleString());
progressText.font = Font.mediumSystemFont(10);
progressStack.addImage(createProgress((resultDe.vaccinations1), colorLightGreen));
widget.addSpacer(3)
let secondDoseStack = widget.addStack()
secondDoseStack.layoutHorizontally()
let amountSecondText = secondDoseStack.addText("Vollständig");
amountSecondText.font = Font.boldSystemFont(12);
secondDoseStack.addSpacer()
let percentSecondText = secondDoseStack.addText(amountSecondPerCent.toLocaleString() + "%")
percentSecondText.font = Font.boldSystemFont(12);
widget.addSpacer(2)
let progressStack2 = widget.addStack();
progressStack2.layoutVertically();
let progressNumberStack2 = widget.addStack();
progressNumberStack2.layoutHorizontally();
const progressText2 = progressNumberStack2.addText(resultDe.vaccinations2.toLocaleString());
progressText2.font = Font.mediumSystemFont(10);
progressStack2.addImage(createProgress(resultDe.vaccinations2, colorLightGreen));
widget.addSpacer(3);
let boosterStack = widget.addStack()
boosterStack.layoutHorizontally()
let amountBoosterText = boosterStack.addText("Booster");
amountBoosterText.font = Font.boldSystemFont(12);
boosterStack.addSpacer()
let percentBoosterText = boosterStack.addText(amountBoosterPerCent.toLocaleString() + "%")
percentBoosterText.font = Font.boldSystemFont(12);
widget.addSpacer(2)
let progressStack3 = widget.addStack();
progressStack3.layoutVertically();
let progressNumberStack3 = widget.addStack();
progressNumberStack3.layoutHorizontally();
const progressText3 = progressNumberStack3.addText(resultDe.vaccinations3.toLocaleString());
progressText3.font = Font.mediumSystemFont(10);
progressStack3.addImage(createProgress(resultDe.vaccinations3, colorLightGreen));
widget.addSpacer(6)
let calendarStack = widget.addStack();
const lastUpdateDate = new Date(resultDe.date);
let description = widget.addText("Stand: " + lastUpdateDate.toLocaleDateString());
description.centerAlignText();
description.font = Font.mediumSystemFont(9);
// widget.addSpacer(8)
}
// get images from local storage or download them once
async function getImage(image) {
let fm = FileManager.local();
let dir = fm.documentsDirectory();
let path = fm.joinPath(dir, image);
if (fm.fileExists(path)) {
return fm.readImage(path);
} else {
// download once
let imageUrl;
switch (image) {
case "vac-logo.png":
imageUrl = "https://i.imgur.com/ZsBNT8E.png";
break;
case "calendar-icon.png":
imageUrl = "https://i.imgur.com/Qp8CEFf.png";
break;
default:
console.log(`Sorry, couldn't find ${image}.`);
}
let req = new Request(imageUrl);
let loadedImage = await req.loadImage();
fm.writeImage(path, loadedImage);
return loadedImage;
}
}
async function getNumbers() {
// Set up the file manager.
const files = FileManager.local();
// Set up cache
const cachePath = files.joinPath(
files.cacheDirectory(),
"api-cache-covid-vaccination-numbers-mopo"
);
const cacheExists = files.fileExists(cachePath);
const cacheDate = cacheExists ? files.modificationDate(cachePath) : 0;
// Get Data
try {
// If cache exists and it's been less than 60 minutes since last request, use cached data.
if (
cacheExists &&
today.getTime() - cacheDate.getTime() < cacheMinutes * 60 * 1000
) {
console.log("Get from Cache");
result = JSON.parse(files.readString(cachePath));
} else {
console.log("Get from API");
const req2 = new Request(
"https://interaktiv.morgenpost.de/data/corona/rki-vaccination-latest.json"
);
result = await req2.loadJSON();
console.log("Write Data to Cache");
try {
files.writeString(cachePath, JSON.stringify(result));
} catch (e) {
console.log("Creating Cache failed!");
console.log(e);
}
}
} catch (e) {
console.error(e);
if (cacheExists) {
console.log("Get from Cache");
result = JSON.parse(files.readString(cachePath));
} else {
console.log("No fallback to cache possible. Due to missing cache.");
}
}
await setTotalVacNoForGermany(result);
}
async function setTotalVacNoForGermany(result) {
for (var i = result.length - 1; i >= 0; i--) {
let currentItem = result[i];
if (currentItem["id"] === "de") {
resultDe = currentItem;
}
}
}
function createProgress(vacNo, color) {
const context = new DrawContext();
context.size = new Size(width, h);
context.opaque = false;
context.respectScreenScale = true;
context.setFillColor(new Color("#d2d2d7"));
const path = new Path();
path.addRoundedRect(new Rect(0, 0, width, h), 3, 2);
context.addPath(path);
context.fillPath();
context.setFillColor(color);
const path1 = new Path();
const path1width =
(width * vacNo) / neededTotalVaccinations > width
? width
: (width * vacNo) / neededTotalVaccinations;
path1.addRoundedRect(new Rect(0, 0, path1width, h), 3, 2);
context.addPath(path1);
context.fillPath();
return context.getImage();
}
//
// Bitte bis zum Ende kopieren
//
@marco79cgn
Copy link
Author

marco79cgn commented Oct 12, 2021

Update 12.10.2021:
Version 1.2.0
Das Update ist fertig. Jetzt ohne Schnick-Schnack, ohne Glaskugel (aka Herdenimmunität). Nur noch Fakten. Und korrekte Zahlen. :)

Bitte neu kopieren von oben. Danke für's Feedback!

// EDIT:
Wer das Skript bereits benutzt hat, bekommt vermutlich eine Fehlermeldung nach der Änderung. Das liegt daran, dass im Cache noch die alten Daten sind (bis zu einer Stunde). Leider hat sich die Datenstruktur der Quelle komplett geändert.

Lösung: entweder bis zu 60 Minuten warten, oder in der Scriptable App im Skript ganz oben die Cache Minuten einmalig auf 0 setzen (statt 60), dann einmal auf Play drücken. Wenn es klappt, wieder zurück auf 60 stellen.

Wenn es in der App selbst bereits funktioniert, dann sollte sich die Anzeige auf dem Homescreen auch zeitnah wieder korrigieren. Einfach etwas Geduld mitbringen. Danke!

@marco79cgn
Copy link
Author

marco79cgn commented Oct 13, 2021

Kleines Update 13.10.2021:
Version 1.2.1
Cache Datei umbenannt, um kurzzeitige Fehler beim Upgrade vom alten Skript zu umgehen.

@lululasse
Copy link

Hi Marco, könnte man noch eine dritte Zeile mit den Zahlen der Booster Impfung integrieren? Arbeitest Du vielleicht schon daran?

@marco79cgn
Copy link
Author

Update 27.11.2021:
Version 1.3.0
Booster Impfungen hinzugefügt

@m33x
Copy link

m33x commented Nov 27, 2021

Danke, funktioniert :-)

@lululasse
Copy link

Danke Marco! Perfekt!

@tonmar77
Copy link

👍🏼

@Special2k
Copy link

Hi! Aktualisiert das Widget bei euch noch? Bei mir ist es mit Stand 27.4.22 stehen geblieben… :-/

@derelvis
Copy link

derelvis commented May 5, 2022

Hier auch. ;-/

@tonmar77
Copy link

tonmar77 commented May 5, 2022

Hier ebenso

@lululasse
Copy link

Immernoch 27.4. :-(

@marco79cgn
Copy link
Author

Sollte gefixt sein. In Zeile 164 musste in der URL ein "s" entfernt werden.

Alte URL:
https://interaktiv.morgenpost.de/data/corona/rki-vaccinations-latest.json

Neue URL:
https://interaktiv.morgenpost.de/data/corona/rki-vaccination-latest.json

Optional bitte das ganze Skript neu kopieren.

@Ted377
Copy link

Ted377 commented May 7, 2022

Error on line 92:71: TypeError: undefined is not an object (evaluating 'amountBoosterPerCent.toLocaleString')

@lululasse
Copy link

gleiches Problem hier nur line 88:71

@marco79cgn
Copy link
Author

Die andere Quelle (ohne s) enthält nur Erst- und Zweitimpfungen, keine Booster.

Ich schau mir das mal in Ruhe an.

Nutze das Widget selbst schon gar nicht mehr, da sich gefühlt seit vielen Wochen sowieso kaum noch was tut bei den Zahlen.

@marco79cgn
Copy link
Author

Die Quelle ist jetzt nicht mehr im json Format, sondern tsv. Richtig nervig zu parsen.

URL: https://cdn.fnki.de/corona-data/vaccinations.rki.tsv

Zudem werden jetzt auch zweite Booster ausgegeben. Ein vierter Balken wäre wohl für das kleine Layout zu viel des Guten.

@lululasse
Copy link

Zweite Booster wäre natürlich super. Das Thema wird in ein paar Wochen sowieso wichtig. Wäre cool wenn Du das noch reinfummeln könntest.

@lululasse
Copy link

Hi Marco, bist Du noch dran an einem Update? Wäre super! Danke im Voraus!

@rafaelmaeuer
Copy link

rafaelmaeuer commented May 24, 2022

Ich habe mal simples TSV-parsing implementiert und den 2. Booster ergänzt: https://gist.github.com/rafaelmaeuer/2321d1543c8e846b13fe38ea0b61b683

Bildschirmfoto 2022-05-23 um 23 57 10

Falls sich jemand bedanken möchte: buymeacoffee.com/rafaelmaeuer ☕️

@lululasse
Copy link

Bekomme Fehlermeldung

Error on line 597:71:
TypeError:
undefined is not an
object (evaluating
l'amountBoosterPer
Cent.toLocaleString‘)

@dennerforen
Copy link

Me too

@rafaelmaeuer
Copy link

rafaelmaeuer commented May 24, 2022

@dennerforen
Copy link

dennerforen commented May 25, 2022

Danke, läuft!

Allerdings steht dort 27.04.2022 als Datum??

@marco79cgn
Copy link
Author

Vielen Dank @rafaelmaeuer! Ich komme aktuell leider gar nicht dazu.
Darf ich deine Änderungen hier oben übernehmen?

@rafaelmaeuer
Copy link

Na klar

@Chaos53925
Copy link

Chaos53925 commented May 30, 2022

Vorschlag für Zeile 34:

  // Add background color
if (!Device.isUsingDarkAppearance()) {
  widget.backgroundColor = new Color("ffffff")
} else {
  widget.backgroundColor = new Color("1c1c1c")
}

Bringt auch den Darkmode rein und prüft auch ob der Darkmode aktiv ist oder nicht

@rafaelmaeuer
Copy link

👍🏻 Werd ich mal testen und ggfs. übernehmen

@Chaos53925
Copy link

Chaos53925 commented May 30, 2022

Habe mich noch ein bisschen mehr reingehangen, da mir die fehlende Automatische Reaktion auf den Wechsel zwischen Dark und Light nicht in ruhe lies:

    // Add background color
widget.backgroundColor = new Color("ffffff");
widget.backgroundColor = Color.dynamic(widget.backgroundColor, new Color("1c1c1c"));

Du kannst mit diesem Code sogar eine Einstellung machen, die den User mittels eines Parameters sogar die Abschaltung des Wechsels zwischen Dark und Light ermöglicht.

@rafaelmaeuer
Copy link

Variante 1 funktionierte leider nicht unter macOS, Variante 2 dafür schon - gefällt mir gut: habe ich in v1.4.3 übernommen

@thorstenleidl
Copy link

Danke @rafaelmaeuer, habe dank deinem tsv parsing meinen fork wiederbeleben können 👍🏻

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