-
-
Save marco79cgn/685804b731e2c8e466501e4b88341286 to your computer and use it in GitHub Desktop.
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! | |
// |
Updates
12.03.2021, 10:40
- Bugfix in URL der Online-Verfügbarkeit (DE) → Bitte Skript neu kopieren
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.
Eine Idee: Schreib doch bitte in die Kommentarzeilen oben noch die Github URL rein, dann klappt das updaten später einfacher.
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
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.
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.
Wie kann man denn die Artikelnummer von den Produkten raus bekommen?
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
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).
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
Update (5. April 2021):
Das Skript gibt jetzt Auskunft über alle drei verfügbaren Selbsttests bei dm (Deutschland).
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?
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.
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
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
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;
}
Hi Marco,
deine Scripts sind echt praktisch, vielleicht hättest du Lust diese auch auf WidgetHub (https://widget-hub.app) verfügbar zu machen ;)
Da dieses Thema 8 Monate später leider wieder aktuell ist, habe ich mal die Produkt IDs für Österreich und Deutschland angepasst.
@marco79cgn
Hi Marco,
wir haben weiterhin Freude an deinen Widgets, daher hier ein paar Hinweise direkt aus der Entwicklungsabteilung bei dm.
-
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 -
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).
-
In AT gibt es auch eine Übersichtsseite, die du als "widget.url" nehmen könntest:
https://www.dm.at/gesundheit/corona-schnell-antikoerpertest -
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
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
Anforderungen
Um deine Store ID zu ermitteln:
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
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.