Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
A Scriptable widget that shows the amount of people who have received the corona vaccination in Germany
// Version 1.4.3
// 30.05.2022
//
// Mit Caching und Fallback
// Neue API mit TSV-Parsing
// Dark-Mode Unterstützung
const resetCache = false;
const cacheMinutes = 60;
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() {
// Add background color
widget.backgroundColor = new Color("ffffff");
widget.backgroundColor = Color.dynamic(widget.backgroundColor, new Color("1c1c1c"));
widget.addSpacer();
const dataFirst = {
text: "Erstimpfung",
quota: resultDe.vaccinations1_quota,
vaccination: resultDe.vaccinations1,
progress: createProgress(resultDe.vaccinations1),
};
createProgressStack(dataFirst);
const dataSecond = {
text: "Zweitimpfung",
quota: resultDe.vaccinations2_quota,
vaccination: resultDe.vaccinations2,
progress: createProgress(resultDe.vaccinations2),
};
createProgressStack(dataSecond);
const dataThird = {
text: "Booster",
quota: resultDe.vaccinations4_quota + " | " + resultDe.vaccinations3_quota,
vaccination: resultDe.vaccinations4 + " | " + resultDe.vaccinations3,
progress: createProgress(resultDe.vaccinations3, resultDe.vaccinations4),
};
createProgressStack(dataThird);
widget.addSpacer(3);
let calendarStack = widget.addStack();
const date = resultDe.date.split('-'); // destructure date
const lastUpdateDate = new Date(date[0], date[1]-1, date[2]); // fixes -1 day
const options = { year: 'numeric', month: '2-digit', day: '2-digit' };
let description = widget.addText("Stand: " + lastUpdateDate.toLocaleDateString('de-DE', options));
description.centerAlignText();
description.font = Font.mediumSystemFont(9);
}
function createProgressStack(data) {
let firstDoseStack = widget.addStack();
firstDoseStack.layoutHorizontally();
let amountFirstText = firstDoseStack.addText(data.text);
amountFirstText.font = Font.boldSystemFont(12);
firstDoseStack.addSpacer();
let percentFirstText = firstDoseStack.addText(data.quota + "%");
percentFirstText.font = Font.boldSystemFont(12);
widget.addSpacer(2);
let progressStack = widget.addStack();
progressStack.layoutVertically();
let progressNumberStack = widget.addStack();
progressNumberStack.layoutHorizontally();
const progressText = progressNumberStack.addText(numberWithDots(data.vaccination));
progressText.font = Font.mediumSystemFont(10);
progressStack.addImage(data.progress);
widget.addSpacer(3);
}
// get images from local storage or download them once (currently not used)
//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;
// }
//}
function numberWithDots(x) {
return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ".");
}
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-tsv"
);
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 && !resetCache &&
today.getTime() - cacheDate.getTime() < cacheMinutes * 60 * 1000
) {
console.log("Get from Cache");
result = files.readString(cachePath);
} else {
console.log("Get from API");
const req2 = new Request(
"https://cdn.fnki.de/corona-data/vaccinations.rki.tsv"
);
result = await req2.loadString();
console.log("Write Data to Cache");
try {
files.writeString(cachePath, result);
} catch (e) {
console.log("Creating Cache failed!");
console.log(e);
}
}
} catch (e) {
console.error(e);
if (cacheExists) {
console.log("Get from Cache");
result = files.readString(cachePath);
} else {
console.log("No fallback to cache possible. Due to missing cache.");
}
}
await setTotalVacNoForGermany(result);
}
async function setTotalVacNoForGermany(result) {
const cols = result.split(/\r?\n/);
const data = cols.map(col => col.split('\t'));
data.pop(); // remove last (empty) element
const name = data[0];
const vals = data[data.length -1];
const date = name.indexOf('date');
const vac1 = name.indexOf('vaccinations1');
const vac2 = name.indexOf('vaccinations2');
const vac3 = name.indexOf('vaccinations3');
const vac4 = name.indexOf('vaccinations4');
const vac1Quota = name.indexOf('vaccinations1_quota');
const vac2Quota = name.indexOf('vaccinations2_quota');
const vac3Quota = name.indexOf('vaccinations3_quota');
const vac4Quota = name.indexOf('vaccinations4_quota');
resultDe = {
date: vals[date],
vaccinations1: vals[vac1],
vaccinations1_quota: vals[vac1Quota],
vaccinations2: vals[vac2],
vaccinations2_quota: vals[vac2Quota],
vaccinations3: vals[vac3],
vaccinations3_quota: vals[vac3Quota],
vaccinations4: vals[vac4],
vaccinations4_quota: vals[vac4Quota],
};
}
function createProgress(firstVacNo, secondVacNo) {
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(colorLightGreen);
const path1 = new Path();
const path1width =
(width * firstVacNo) / neededTotalVaccinations > width
? width
: (width * firstVacNo) / neededTotalVaccinations;
path1.addRoundedRect(new Rect(0, 0, path1width, h), 3, 2);
context.addPath(path1);
context.fillPath();
if (secondVacNo) {
const path2 = new Path();
const path2width =
(width * secondVacNo) / neededTotalVaccinations > width
? width
: (width * secondVacNo) / neededTotalVaccinations;
path2.addRoundedRect(new Rect(0, 0, path2width, h), 3, 2);
context.addPath(path2);
context.setFillColor(colorDarkGreen)
context.fillPath();
}
return context.getImage();
}
//
// Bitte bis zum Ende kopieren
//
@Special2k
Copy link

Special2k commented May 24, 2022

Geil, vielen Dank!

@Special2k
Copy link

Special2k commented May 24, 2022

Hmm bekomme den Fehler: „ Exception Occurred Error on line 286:24: The widget is
already presented.“

@lululasse
Copy link

lululasse commented May 24, 2022

Bekomme Fehlermeldung

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

@lululasse
Copy link

lululasse commented May 24, 2022

Jetzt bekomme ich die gleiche Fehlermeldung wie Spezial2K und das Datum der Daten ist der 27.4. und ohne 4. Impfung.

@rafaelmaeuer
Copy link
Author

rafaelmaeuer commented May 24, 2022

Merkwürdig, ich hatte das caching komplett deaktiviert, ist in v1.4.1 wieder aktiv - sonst Widget mal löschen und neu anlegen…

@SchneHa
Copy link

SchneHa commented May 24, 2022

1.4.1 rennt! Danke.

@rafaelmaeuer
Copy link
Author

rafaelmaeuer commented May 25, 2022

v1.4.2: code refactoring + cleanup

@SchneHa
Copy link

SchneHa commented May 25, 2022

Wunderbar, und wenn man in den Zeilen 81 und 84 die Fontgröße 11 statt 12 nimmt passt auch alles ins Fenster. Font.boldSystemFont(11)

@Special2k
Copy link

Special2k commented May 25, 2022

Läuft! Danke! :-D

@bondskin
Copy link

bondskin commented May 25, 2022

Super. Danke

@lululasse
Copy link

lululasse commented May 25, 2022

Supi! Läuft! Vielen Dank!

@princemaxwell
Copy link

princemaxwell commented May 27, 2022

Im DarkMode leider keine Schrift zu lesen…

hab die Zeile auskommentiert.

//if (Device.isUsingDarkAppearance()) { // widget.backgroundColor = new Color("1c1c1e"); //}

@rafaelmaeuer
Copy link
Author

rafaelmaeuer commented May 28, 2022

Interessant, bei mir macht das keine Probleme:

Edit: @princemaxwell probier mal v1.4.3, Chaos43925 hat diese Verbesserung vorgeschlagen: https://gist.github.com/marco79cgn/b5f291d6242a2c530e56c748f1ae7f2c#gistcomment-4184051

@Chaos53925
Copy link

Chaos53925 commented May 31, 2022

sag mal, kannst du die Texte so anpassen, dass man deren Farbe auch Manuell ändern kann? Dann könnte ich eine Methode vorschlagen, wie man einstellen kann, ob man dauerhaft den Darkmode oder dauerhaft den Lightmode haben will.

Edit: Vergiss es. Ich habe einen Weg gefunden. Ist aber eine menge Code, die Vorschlagen müsste...

@Chaos53925
Copy link

Chaos53925 commented Jun 1, 2022

um diesen Kommentar klein zu halten habe ich für den neuen Code einen Fork deines Forks erstellt und meinen Code dort untergebracht.

https://gist.github.com/Chaos53925/5617c8a70ddbaec918b2d6804755c92f

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