Skip to content

Instantly share code, notes, and snippets.

@marco79cgn
Last active December 15, 2021 22:55
Show Gist options
  • Star 36 You must be signed in to star a gist
  • Fork 6 You must be signed in to fork a gist
  • Save marco79cgn/685804b731e2c8e466501e4b88341286 to your computer and use it in GitHub Desktop.
Save marco79cgn/685804b731e2c8e466501e4b88341286 to your computer and use it in GitHub Desktop.
Shows the available amount of Covid Tests in your local drug store and online
let country = "de"; // replace with 'at' for shops in Austria
let storeId = 251;
let param = args.widgetParameter;
if (param != null && param.length > 0) {
if (param.indexOf(";") > 0) {
const paramSplit = param.split(";");
storeId = paramSplit[0];
country = paramSplit[1].toLowerCase();
} else {
storeId = param;
}
}
const widget = new ListWidget();
const storeInfo = await fetchStoreInformation();
const storeCapacity = await fetchAmountOfPaper();
const isOnlineAvailable = await isAvailableOnline();
await createWidget();
// used for debugging if script runs inside the app
if (!config.runsInWidget) {
await widget.presentSmall();
}
Script.setWidget(widget);
Script.complete();
// build the content of the widget
async function createWidget() {
const logoImg = await getImage("dm-logo.png");
widget.setPadding(6, 10, 8, 10);
const titleFontSize = 12;
const detailFontSize = 36;
const logoStack = widget.addStack();
logoStack.addSpacer();
const logoImageStack = logoStack.addStack();
logoStack.layoutHorizontally();
logoImageStack.backgroundColor = new Color("#ffffff", 1.0);
logoImageStack.cornerRadius = 8;
const wimg = logoImageStack.addImage(logoImg);
wimg.imageSize = new Size(32, 32);
wimg.rightAlignImage();
const paperText = widget.addText("CORONA-SELBSTTEST");
paperText.font = Font.semiboldRoundedSystemFont(11);
widget.addSpacer(8);
const icon = await getImage("covidtest.png");
let row = widget.addStack();
row.layoutHorizontally();
row.addSpacer(2);
const iconImg = row.addImage(icon);
iconImg.imageSize = new Size(32, 32);
row.addSpacer(12);
let column = row.addStack();
column.layoutVertically();
const storeText = column.addText("STORE");
storeText.textOpacity = 0.8;
storeText.font = Font.mediumRoundedSystemFont(10);
const packageCount = column.addText(storeCapacity.toString());
if (storeCapacity > 999) {
packageCount.font = Font.mediumRoundedSystemFont(16);
} else {
packageCount.font = Font.mediumRoundedSystemFont(19);
}
// packageCount.minimumScaleFactor = 0.8
if (storeCapacity < 5) {
packageCount.textColor = new Color("#E50000");
} else {
packageCount.textColor = new Color("#00CD66");
}
row.addSpacer(12);
let column2 = row.addStack();
column2.layoutVertically();
const onlineText = column2.addText("ONLINE");
onlineText.textOpacity = 0.8;
onlineText.font = Font.mediumRoundedSystemFont(10);
column2.addSpacer(3);
let onlineAvailableIcon;
if (isOnlineAvailable) {
onlineAvailableIcon = "✅";
} else {
onlineAvailableIcon = "⛔️";
}
const onlineAvailableText = column2.addText(onlineAvailableIcon);
if (storeCapacity > 999) {
onlineAvailableText.font = Font.regularSystemFont(11);
} else {
onlineAvailableText.font = Font.regularSystemFont(13);
}
widget.addSpacer(10);
const row2 = widget.addStack();
row2.layoutVertically();
const street = row2.addText(storeInfo.address.street);
street.font = Font.regularSystemFont(11);
street.minimumScaleFactor = 0.8;
street.lineLimit = 2;
const zipCity = row2.addText(
storeInfo.address.zip + " " + storeInfo.address.city
);
zipCity.font = Font.regularSystemFont(11);
zipCity.minimumScaleFactor = 0.8;
zipCity.lineLimit = 1;
let currentTime = new Date().toLocaleTimeString("de-DE", {
hour: "numeric",
minute: "numeric",
});
let currentDay = new Date().getDay();
let isOpen;
if (currentDay > 0) {
const todaysOpeningHour =
storeInfo.openingHours[currentDay - 1].timeRanges[0].opening;
const todaysClosingHour =
storeInfo.openingHours[currentDay - 1].timeRanges[0].closing;
const range = [todaysOpeningHour, todaysClosingHour];
isOpen = isInRange(currentTime, range);
} else {
isOpen = false;
}
let shopStateText;
if (isOpen) {
shopStateText = row2.addText("GEÖFFNET");
shopStateText.textColor = new Color("#00CD66");
} else {
shopStateText = row2.addText("GESCHLOSSEN");
shopStateText.textColor = new Color("#E50000");
}
shopStateText.font = Font.semiboldSystemFont(10);
}
// fetches the amount of milk packages
async function fetchAmountOfPaper() {
let url;
let counter = 0;
if (country.toLowerCase() === "at") {
// Austria
const array = ["188896","114408"];
for (var i = 0; i < array.length; i++) {
let currentItem = array[i];
url =
"https://products.dm.de/store-availability/AT/products/dans/" +
currentItem +
"/stocklevel?storeNumbers=" +
storeId;
let req = new Request(url);
let apiResult = await req.loadJSON();
if (req.response.statusCode == 200) {
counter += apiResult.storeAvailability[0].stockLevel;
}
}
} else {
// Germany
url = "https://products.dm.de/store-availability/DE/availability?dans=798989,819862,797046,800021&storeNumbers=" + storeId;
const req = new Request(url);
const apiResult = await req.loadJSON();
for (var i in apiResult.storeAvailabilities) {
if (apiResult.storeAvailabilities[i][0].stockLevel) {
counter += apiResult.storeAvailabilities[i][0].stockLevel;
}
}
}
return counter;
}
async function isAvailableOnline() {
let url;
if (country.toLowerCase() === "at") {
url =
"https://products.dm.de/product/at/search?productQuery=%3Arelevance%3Adan%3A188896";
} else {
url =
"https://products.dm.de/product/de/search?productQuery=%3Arelevance%3Adan%3A796724&purchasableOnly=false&hideFacets=false&hideSorts=false&pageSize=30";
}
let req = new Request(url);
let apiResult = await req.loadJSON();
if (req.response.statusCode == 200) {
for (var i = 0; i < apiResult.products.length; i++) {
if (apiResult.products[i].purchasable) {
return true;
}
}
}
return false;
}
// fetches information of the configured store, e.g. opening hours, address etc.
async function fetchStoreInformation() {
let url;
if (country.toLowerCase() === "at") {
url =
"https://store-data-service.services.dmtech.com/stores/item/at/" +
storeId;
widget.url =
"https://www.dm.at/keine-marke-coronavirus-antigen-schnelltest-fuer-zuhause-p2099999042083.html";
} else {
url =
"https://store-data-service.services.dmtech.com/stores/item/de/" +
storeId;
widget.url =
"https://www.dm.de/gesundheit/corona-schnell-antikoerpertest";
}
let req = new Request(url);
let apiResult = await req.loadJSON();
return apiResult;
}
// checks whether the store is currently open or closed
function isInRange(value, range) {
return value >= range[0] && value <= range[1];
}
// get images from local filestore 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 "dm-logo.png":
imageUrl =
"https://upload.wikimedia.org/wikipedia/commons/thumb/5/50/Dm_Logo.svg/300px-Dm_Logo.svg.png";
break;
case "covidtest.png":
imageUrl = "https://i.imgur.com/HFQe5io.png";
break;
default:
console.log(`Sorry, couldn't find ${image}.`);
}
let iconImage = await loadImage(imageUrl);
fm.writeImage(path, iconImage);
return iconImage;
}
}
// helper function to download an image from a given url
async function loadImage(imgUrl) {
const req = new Request(imgUrl);
return await req.loadImage();
}
//
// make sure to copy everything!
//
@marco79cgn
Copy link
Author

marco79cgn commented Mar 11, 2021

Intro

Das dm Corona Selbsttest Widget zeigt die Vorräte an Corona Schnell-/Selbsttests in deiner nächsten lokalen dm Drogerie sowie eine Online-Verfügbarkeit.

Die Store ID für deine gewünschte dm Drogerie kann in den Einstellungen des Widgets als Parameter konfiguriert werden. Dadurch können auch mehrere Widgets parallel auf dem Homescreen eingerichtet werden für unterschiedliche Geschäfte.

Updates

12.03.2021, 10:40

  • Bugfix in URL der Online-Verfügbarkeit (DE) → Bitte Skript neu kopieren

Anforderungen

Um deine Store ID zu ermitteln:

  • Öffne die dm Shop Finder Seite
  • Gib deine Postleitzahl ein und wähle den gewünschten Markt aus
  • Klick dann auf "Weitere Details"
    Die store id steht dann in der Browser URL, zum Beispiel "https://www.dm.de/store/de-2449/koeln/hohenzollernring-58" ist die store id 2449. Diese Nummer kannst du in den Einstellungen des Widgets auf dem Homescreen unter "Parameter" eintragen. Für Österreich bitte hinter die Store ID das Länderkürzel "at" ergänzen, getrennt durch Semikolon, z.B. "251;at"

Installation

  • Kopiere den Source code von oben (klick vorher auf "raw" oben rechts)
  • Öffne die Scriptable app
  • Klick auf das "+" Symbol oben rechts und füge das kopierte Skript ein
  • Klick auf den Titel des Skripts ganz oben und vergebe einen Namen (z.B. "Schnelltests")
  • Speichere das Skript durch Klick auf "Done" oben links
  • Gehe auf deinen iOS Homescreen und drücke irgendwo lang, um in den "wiggle mode" zu kommen (mit dem man auch die App Symbole anordnen kann)
  • Drücke das "+" Symbol oben links, blättere dann nach unten zu "Scriptable" (Liste ist alphabetisch), wähle die erste Widget Größe (small) und drück unten auf "Widget hinzufügen"
  • Drücke auf das Widget, um seine Einstellungen zu bearbeiten (optional lang drücken, wenn der Wiggle Modus schon beendet wurde)
  • Wähle unter "Script" das oben erstellte aus (Schnelltests)
  • Gib als "Parameter" die ermittelten Store ID ein, z.B. "180" für Mainz
  • Optional: Für Geschäfte in Österreich bitte hinter die Store ID das Länderkürzel "at" schreiben, z.B. "251;at"

Danke

Großer Dank an @simonbs für großartige Apps wie Scriptable, DataJar oder Jayson.
Icon erstellt von Freepik (www.flaticon.com)

Disclaimer

Es handelt sich um ein von mir selbst entwickeltes Spaßprojekt, kein offizielles Produkt der dm Drogerie. Ich stehe in keinerlei Beziehung zu dm und bekomme weder Provision noch kostenloses Schnelltests.

@cru7aa
Copy link

cru7aa commented Mar 12, 2021

352E2D85-85FD-47F9-99B6-CE88F072127D
83534205-C612-474D-8116-89B3E6BC8980

Das Widget zeigt einen grünen Haken bei „online“, aber online steht „ Derzeit online nicht lieferbar“

@marco79cgn
Copy link
Author

Updates

12.03.2021, 10:40

  • Bugfix in URL der Online-Verfügbarkeit (DE) → Bitte Skript neu kopieren

@cru7aa
Copy link

cru7aa commented Mar 12, 2021

Updates

12.03.2021, 10:40

  • Bugfix in URL der Online-Verfügbarkeit (DE) → Bitte Skript neu kopieren

Funktioniert jetzt bei mir… Dankeschön
C9A98327-7280-4238-B2BB-2ACCF3FD33DC

@chrischdjan
Copy link

Hi, sehr coole Idee, vielen Dank!

Aldi-Süd hat (neuerdings?) auch eine Verfügbarkeitsabfrage für den Schnelltest:

https://verfuegbarkeit.aldi-sued.de/product/490000000000710432

Könnte man das Skript darauf anpassen? Bin leider nicht tief genug in der Materie drin um es selber herauszufummeln. Unter anderem weil die Seite der API nur Längen und Breitengrad mitgibt und dann eine Liste der nächsten Aldis inkl. Verfügbarkeits-Flag ausgibt.

@JDTm
Copy link

JDTm commented Mar 12, 2021

Eine Idee: Schreib doch bitte in die Kommentarzeilen oben noch die Github URL rein, dann klappt das updaten später einfacher.

@Smotherer007
Copy link

Smotherer007 commented Mar 14, 2021

Servus,

Ich hab mich dank deiner Inspiration auch mal dran versucht. Hier mal ein Versuch zur Darstellung der Verfügbarkeit von Impfterminen.

https://gist.github.com/Smotherer007/a079a7e675b90fb9efa42e1cc71785ac

@julawo
Copy link

julawo commented Mar 31, 2021

Hallo!
Vielen Dank für das Widget. :-)
Mir ist aufgefallen, dass es inzwischen verschiedene Selbsttests bei dm gibt. Wenn ich das richtig sehe manche im Markt und online und den mittleren nur im Onlineshop. Das Widget zählt bislang ausschließlich den von Boson 796724, d.h. die anderen beiden können noch ergänzt werden: 797046, 797729.
Screenshot 2021-03-31 145942

@7samurai
Copy link

Hallo!
Vielen Dank für das Widget. :-)
Mir ist aufgefallen, dass es inzwischen verschiedene Selbsttests bei dm gibt. Wenn ich das richtig sehe manche im Markt und online und den mittleren nur im Onlineshop. Das Widget zählt bislang ausschließlich den von Boson 796724, d.h. die anderen beiden können noch ergänzt werden: 797046, 797729.
Screenshot 2021-03-31 145942

Wie kann man denn die Artikelnummer von den Produkten raus bekommen?

@julawo
Copy link

julawo commented Mar 31, 2021

Die steht jeweils am Ende der Produktbeschreibung: dm-Artikelnummer
w41D01A8E-E923-42F7-BE14-865FABE5B33F

@clemenstyp
Copy link

Hallo, vielen Dank für deinen Code. Ich habe den Code so modifiziert, dass jetzt die unterschiedlichen Tests abgefragt werden. Dazu gibt es am Anfang ein Array in dem die Artikelnummern abgelegt sind. Du kannst den Code gerne in deinen Code übernehmen (es gibt bei Gist ja leider kein Pull-Request Feature)

https://gist.github.com/clemenstyp/9f4576a01ee0710afa85d6f846e19617

@marco79cgn
Copy link
Author

Sorry, hatte die letzten Tage wenig Zeit, mich darum zu kümmern.

Das Skript war von Beginn an in der Lage, mehrere dan Nummern zu verarbeiten, denn das war damals beim Klopapier-Widget ebenfalls nötig.

Für mehrere dan muss man lediglich die URL des API-Calls erweitern, indem man die zusätzlichen Nummern kommasepariert dahinter setzt. So sah die URL z.B. beim Klopapier aus:
url = https://products.dm.de/store-availability/DE/availability?dans=595420,708997,137425,28171,485698,799358,863567,452740,610544,846857,709006,452753,879536,452744,485695,853483,594080,504606,593761,525943,842480,535981,127048,524535&storeNumbers=' + storeId

@clemenstyp Vielen Dank! Der Vorteil, wenn man die dan an die URL dran hängt ist, dass man nur einen einzigen API call machen muss. Leider gibt es das Feature nicht in Österreich, weshalb ich es da genauso gemacht habe wie du (also ein Array an Zahlen und für jeden einen einzelnen Call).

@JDTm
Copy link

JDTm commented Apr 4, 2021

Für alle anderen macht zwei Anpassungen:

Für Deutschland Zeile 168 anpassen: die Nummern dazu schreiben ,797046,797729
d.h. die Zeile 168 ist dann:
"https://products.dm.de/store-availability/DE/availability?dans=796724,797046,797729&storeNumbers=" +
Zeile 215: die URL anpassen: https://www.dm.de/search?query=corona%20schnelltest&searchType=product

@marco79cgn
Copy link
Author

Update (5. April 2021):
Das Skript gibt jetzt Auskunft über alle drei verfügbaren Selbsttests bei dm (Deutschland).

@JDTm
Copy link

JDTm commented Apr 9, 2021

Es gibt jetzt auch ein 5er Pack Schnelltests: 799057
"https://products.dm.de/store-availability/DE/availability?dans=796724,797046,797729,799057&storeNumbers=" +

Das 5er Pack ist aber Online Verfügbar, wird aber trotzdem als nicht verfügbar angezeigt?

@Bernd95
Copy link

Bernd95 commented Apr 10, 2021

Da man bei Widgets leider nicht weiß, wann sie sich aktualisiert haben, fände ich es klasse, wenn Datum und Uhrzeit der Abfrage mit angezeigt werden könnten. Links oben neben dem Logo wäre ja noch Platz dafür.

@JDTm
Copy link

JDTm commented Apr 21, 2021

Die aktuellen Nummern sind: 796724,797046,798989,799454,800208,800021,796724,799462
Zeile 168: https://products.dm.de/store-availability/DE/availability?dans=796724,797046,798989,799454,800208,800021,796724,799462&storeNumbers=" +

und es gibt jetzt auch ne eigene Shop URL (Zeile 215):
https://www.dm.de/gesundheit/corona-schnell-antikoerpertest

@tofi86
Copy link

tofi86 commented Apr 21, 2021

Für alle die ein NaN bei der Verfügbarkeit angezeigt bekommen:

Ändert die Zeilen 172-174

    for (var i in apiResult.storeAvailabilities) {
      counter += apiResult.storeAvailabilities[i][0].stockLevel;
    }

in

    for (var i in apiResult.storeAvailabilities) {
      if (apiResult.storeAvailabilities[i][0].stockLevel) {
        counter += apiResult.storeAvailabilities[i][0].stockLevel;
      }
    }

Hier mein funktionierender Fork mit geänderten DANS und URL's:
https://gist.github.com/tofi86/5e95708cff0b1746b57806a78bdb2eda

@marco79cgn
Copy link
Author

Update (22.04.2021):
Skript aktualisiert. Die Änderungen von @JDTm und @tofi86 sind jetzt oben angepasst. Danke dafür.

@Bernd95
Copy link

Bernd95 commented Apr 24, 2021

Ich habe in das aktualisierte Skript links oben noch Datum und Uhrzeit des letzten Ladevorgangs ergänzt. Dazu habe ich statt Zeile 35 folgende Zeilen verwendet (leider weiß ich nicht, wie man hier Code vernünftig formatiert einfügt):

  let row1 = widget.addStack();
  row1.layoutHorizontally();
  const today = new Date();
  const dateTime = row1.addText( formatDate(today) );
  dateTime.font = Font.regularSystemFont(9);
  dateTime.textColor = Color.lightGray()  
  row1.addSpacer();
  const logoStack = row1.addStack();

Und dann ganz am Schluss des Skripts noch diese Ergänzung:

function formatDate(timestamp) {
  let minutes = timestamp.getMinutes() < 10 ? "0" + timestamp.getMinutes() : timestamp.getMinutes();
  let day = timestamp.getDate() < 10 ? "0" + timestamp.getDate() : timestamp.getDate();
  let month = timestamp.getMonth() + 1;
  month = month < 10 ? "0" + month : month;
  let date = day + "." + month + "." + timestamp.getFullYear() + " " + timestamp.getHours() + ":" + minutes;
  return date;
} 

@apfelbasti
Copy link

Bei mir sind die Zahlen für Masken und Schnelltest irgendwie angeschnitten. Ich nutze ein iPhone SE 2. Gen
CB2E0ADA-58BB-4265-8C35-D01630FFD7B2

@wickenico
Copy link

Hi Marco,
deine Scripts sind echt praktisch, vielleicht hättest du Lust diese auch auf WidgetHub (https://widget-hub.app) verfügbar zu machen ;)

@marco79cgn
Copy link
Author

marco79cgn commented Nov 19, 2021

Da dieses Thema 8 Monate später leider wieder aktuell ist, habe ich mal die Produkt IDs für Österreich und Deutschland angepasst.

@mdirnberger
Copy link

mdirnberger commented Dec 9, 2021

@marco79cgn
Hi Marco,
wir haben weiterhin Freude an deinen Widgets, daher hier ein paar Hinweise direkt aus der Entwicklungsabteilung bei dm.

  1. Wir haben für die Produktsuche auf eine neue API umgestellt (die alte API wird wohl nächste Woche abgeschaltet):
    Für dieses Skript wären die neuen URLs:
    DE: https://product-search.services.dmtech.com/de/search?query=schnelltest&allCategories.id=031500
    (das ist besser als die Produkte über DAN einzeln abzurufen )
    AT: https://product-search.services.dmtech.com/at/search?query=schnelltest&allCategories.id=031500

  2. Wenn du das Skript ganz dynamisch gestalten willst, kannst du einfach die DAN für die Produkte aus der Suchantwort extrahieren und diese dann für die Abfrage der Filialverfügbarkeit nutzen (so muss du keine einzelnen Produkte über die DAN im Skript referenzieren, denn es können immer wieder Produkte hinzukommen).

  3. In AT gibt es auch eine Übersichtsseite, die du als "widget.url" nehmen könntest:
    https://www.dm.at/gesundheit/corona-schnell-antikoerpertest

  4. Inzwischen kannst du für AT dieselbe URL nutzen wie für DE, um die Filialverfügbarkeit abzufragen, hier ein Beispiel:
    https://products.dm.de/store-availability/at/availability?dans=188896,114408&storeNumbers=849

Viele Grüße,
Mike

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