-
-
Save marco79cgn/c3410c8ecc8cb0e9f87409cee7b87338 to your computer and use it in GitHub Desktop.
// Variables used by Scriptable. | |
// These must be at the very top of the file. Do not edit. | |
// icon-color: yellow; icon-glyph: magic; | |
// Maske Icon erstellt von "Freepik" (www.flaticon.com/de) | |
let country = "de"; | |
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("FFP2 MASKEN"); | |
paperText.font = Font.semiboldRoundedSystemFont(12); | |
widget.addSpacer(8); | |
const icon = await getImage("face-mask.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(15); | |
} else { | |
packageCount.font = Font.mediumRoundedSystemFont(19); | |
} | |
// packageCount.minimumScaleFactor = 0.8 | |
if (storeCapacity < 30) { | |
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 toilet paper packages | |
async function fetchAmountOfPaper() { | |
let url; | |
let counter = 0; | |
if (country.toLowerCase() === "at") { | |
// Austria | |
const array = ["820"]; | |
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=725140,777852,726160,755560,734782,774314,764099,755161,755178,725716,781275,722416&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%3A820"; | |
} else { | |
url = | |
"https://products.dm.de/product/de/search?productQuery=%3Arelevance%3Adan%3A726160%3Adan%3A722332%3Adan%3A722416%3Adan%3A755161%3Adan%3A755560%3Adan%3A755178%3Adan%3A725140%3Adan%3A734782%3Adan%3A725716&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/mivolis-ffp2-atemschutzmaske-p4058172788109.html"; | |
} else { | |
url = | |
"https://store-data-service.services.dmtech.com/stores/item/de/" + | |
storeId; | |
widget.url = | |
"https://www.dm.de/gesundheit/mundschutz-stoffmasken#ffp2-produkte"; | |
} | |
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 "face-mask.png": | |
imageUrl = "https://i.imgur.com/sXG04J0.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(); | |
} | |
// | |
// please make sure to copy everything | |
// |
@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=ffp2&allCategories.id=031400
(das ist besser als die Produkte über DAN einzeln abzurufen, mit DAN wäre es so: https://product-search.services.dmtech.com/de/search?dan=725140&dan=734782&dan=755161 )
AT: https://product-search.services.dmtech.com/at/search?query=ffp2&allCategories.id=031400
(einzeln mit DAN: https://product-search.services.dmtech.com/at/search?dan=820&dan=247932 ) -
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/mundschutz-stoffmasken -
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=820,247932&storeNumbers=849
Viele Grüße,
Mike
@mdirnberger
Hey Mike, vielen Dank für die ganzen Infos und Tipps! Bin aktuell ziemlich beschäftigt und das spart mir viel Zeit, alles selbst rausfinden zu müssen.
Werde versuchen, die Skripte zeitnah anzupassen. Die Optimierungsidee klingt auch super ohne die hart kodierten Produkte/DAN. 👍
Viele Grüße,
Marco
@marco79cgn vielen Dank für das Widget und ähnliche, bin beim Release damals bereits drauf gestoßen und vor kurzem kam ich wieder darauf wegen einem anderen dm Artikel.
Die "dan" Nummer finden und den Script anpassen habe ich für mich hinbekommen.
Was ich aber nicht hinbekomme ist ein eigenes .png hinzuzufügen. Gibt es eine anmeldefreie Seite woraus ich Links benutzen kann?
Bzw. wie funktioniert das mit dem "FileManager", wenn ich mir zum Beispiel eigene Bildchen in einem iCloud Ordner anlege?
Also aus dem lokalen Speicher, wobei mir ein Online-Speicher lieber wäre, falls ich das Widget mit Freunden teile.
Geht das auch mit ner Dropboxfreigabe z.b.?
Viele Grüße
AL
Intro
Das dm FFP2 Masken Widget zeigt die Vorräte an FFP2 Schutzmasken in deiner nächsten lokalen dm Drogerie sowie eine Online-Verfügbarkeit. Nachdem FFP2 Schutzmasken verpflichtend in Bayern sind, werden vermutlich andere Bundesländer nachziehen.
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.
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 FFP2 Masken.
Updates
13.01.2021, 12:24