-
-
Save marco79cgn/b13719df059d1e8d3277af8216a4d340 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: green; icon-glyph: magic; | |
// dm Klopapier & Mehl Widget | |
// | |
// Copyright (C) 2020 by marco79 <[email protected]> | |
// | |
// Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted. | |
// | |
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL | |
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, | |
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER | |
// IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE | |
// OF THIS SOFTWARE. | |
// | |
// Wheat flour icon made by Freepik from www.flaticon.com and modified by achisto | |
// Toilet paper icon made by boettges | |
let country = 'de' // replace with 'at' for shops in Austria | |
let storeId = "D4IA" | |
let param = args.widgetParameter | |
if (param != null && param.length > 0) { | |
storeId = param | |
} | |
const widget = new ListWidget()// | |
const storeInfo = await fetchStoreInformation() | |
const storeCapacityPaper = await fetchAmountOfPaper() | |
const storeCapacityFlour = await fetchAmountOfWheatFlour() | |
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,6,6,6) | |
const titleFontSize = 12 | |
const detailFontSize = 36 | |
const logoStack = widget.addStack() | |
const shopStateStack = logoStack.addStack() | |
shopStateStack.layoutVertically() | |
shopStateStack.addSpacer(10) | |
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 = shopStateStack.addText('GEÖFFNET') | |
shopStateText.textColor = new Color("#00CD66") | |
logoStack.addSpacer(36) | |
} else { | |
shopStateText = shopStateStack.addText('GESCHLOSSEN') | |
shopStateText.textColor = new Color("#E50000") | |
logoStack.addSpacer(10) | |
} | |
shopStateText.font = Font.boldRoundedSystemFont(12) | |
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() | |
widget.addSpacer(6) | |
// toilet paper | |
const toiletPaperIcon = await getImage('toilet-paper.png') | |
let row = widget.addStack() | |
row.layoutHorizontally() | |
row.addSpacer(2) | |
const toiletPaperImg = row.addImage(toiletPaperIcon) | |
toiletPaperImg.imageSize = new Size(30,30) | |
row.addSpacer(13) | |
let column = row.addStack() | |
column.layoutVertically() | |
const paperText = column.addText("KLOPAPIER") | |
paperText.font = Font.mediumRoundedSystemFont(11) | |
const packageCount = column.addText(storeCapacityPaper.toString()) | |
packageCount.font = Font.mediumRoundedSystemFont(18) | |
if (storeCapacityPaper < 30) { | |
packageCount.textColor = new Color("#E50000") | |
} else { | |
packageCount.textColor = new Color("#00CD66") | |
} | |
widget.addSpacer(4) | |
// wheat flour | |
const flourIcon = await getImage('wheat-flour.png') | |
let row2= widget.addStack() | |
row2.layoutHorizontally() | |
row2.addSpacer(3) | |
const flourImg = row2.addImage(flourIcon) | |
flourImg.imageSize = new Size(30,30) | |
row2.addSpacer(12) | |
let column2 = row2.addStack() | |
column2.layoutVertically() | |
const flourText = column2.addText("MEHL") | |
flourText.font = Font.mediumRoundedSystemFont(11) | |
const flourPackageCount = column2.addText(storeCapacityFlour.toString()) | |
flourPackageCount.font = Font.mediumRoundedSystemFont(18) | |
if (storeCapacityFlour < 30) { | |
flourPackageCount.textColor = new Color("#E50000") | |
} else { | |
flourPackageCount.textColor = new Color("#00CD66") | |
} | |
widget.addSpacer(4) | |
// shop info | |
const row3 = widget.addStack() | |
row3.layoutVertically() | |
const street = row3.addText(storeInfo.address.street) | |
street.font = Font.regularSystemFont(10) | |
const zipCity = row3.addText(storeInfo.address.zip + " " + storeInfo.address.city) | |
zipCity.font = Font.regularSystemFont(10) | |
} | |
// fetches the amount of toilet paper packages | |
async function fetchAmountOfPaper() { | |
let url | |
let counter = 0 | |
if (country.toLowerCase() === 'at') { | |
// Austria | |
const array = ["156754", "180487", "194066", "188494", "194144", "273259", "170237", "232201", "170425", "283216", "205873", "205874", "249881", "184204"] | |
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/availabilities/tiles?dans=975799,877955,949772,799358,952683,952170,951954,870886,863567,985619,535981,879536,856834,853483,919184,127048,836029,777180,836029,871279,593761,137425,593802,876541&pickupStoreId=' + storeId | |
const req = new Request(url) | |
const apiResult = await req.loadJSON() | |
const availabilities = apiResult.pickupStoreAvailabilitiesByDan | |
let totalAmount = parseInt('0',10) | |
for (const key in availabilities) { | |
if(availabilities.hasOwnProperty(key)){ | |
let stockLevel = `${availabilities[key].stockLevel}` | |
if(stockLevel !== "undefined") { | |
totalAmount = parseInt(totalAmount,10) + parseInt(stockLevel,10) | |
} | |
} | |
} | |
counter = totalAmount | |
} | |
return counter | |
} | |
// fetches the amount of wheat flour packages | |
async function fetchAmountOfWheatFlour() { | |
let url | |
let counter = 0 | |
if (country.toLowerCase() === 'at') { | |
// Austria | |
const array = ["178501", "178491", "178498", "178484", "171412", "178489", "295198"] | |
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/availabilities/tiles?dans=459912,755784,468178,468120,468168,721676,531500,849142&pickupStoreId=' + storeId | |
const req = new Request(url) | |
const apiResult = await req.loadJSON() | |
const availabilities = apiResult.pickupStoreAvailabilitiesByDan | |
let totalAmount = parseInt('0', 10) | |
for (const key in availabilities) { | |
if (availabilities.hasOwnProperty(key)) { | |
let stockLevel = `${availabilities[key].stockLevel}` | |
if (stockLevel !== 'undefined') { | |
totalAmount = parseInt(totalAmount, 10) + parseInt(stockLevel, 10) | |
} | |
} | |
} | |
counter = totalAmount | |
} | |
return counter | |
} | |
// 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/search?query=toilettenpapier&searchType=product' | |
} else { | |
url = 'https://store-data-service.services.dmtech.com/stores/item/' + storeId | |
widget.url = 'https://www.dm.de/search?query=toilettenpapier&searchType=product' | |
} | |
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 'toilet-paper.png': | |
imageUrl = "https://i.imgur.com/Uv1qZGV.png" | |
break | |
case 'wheat-flour.png': | |
imageUrl = "https://i.imgur.com/gwWtMWn.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() | |
} | |
// End of script | |
// Bitte bis zum Ende alles markieren und kopieren |
Bei mir funktioniert die Aktualisierung nicht ganz. Wenn ich das Skript in Scriptable laufen lassen zeigt er mir aktuelle Zahlen, aber auf dem Widget sind noch alte Zahlen zu sehen. Normalerweise (bei anderen Scripts) kann ich auf das Widget klicken und dann läuft es neu und aktualisiert sich, aber hier geht das leider nicht.
Trotzdem ein fantastisches Widget, bin ein Riesen Fan davon!
Bei mir funktioniert die Aktualisierung nicht ganz. Wenn ich das Skript in Scriptable laufen lassen zeigt er mir aktuelle Zahlen, aber auf dem Widget sind noch alte Zahlen zu sehen. Normalerweise (bei anderen Scripts) kann ich auf das Widget klicken und dann läuft es neu und aktualisiert sich, aber hier geht das leider nicht.
Trotzdem ein fantastisches Widget, bin ein Riesen Fan davon!
Hast du die ID oben im Skript angepasst? Denn da ist standardmäßig eine fest gesetzte drin (251), die von jener in den Widget Einstellungen überschrieben wird. Das passiert allerdings nicht, wenn du das Skript manuell in der App ausführst. Worauf ich hinaus will: vergleichst du die selben Shops?
Prinzipiell aktualisiert sich das Widget alle 5-10 Minuten von alleine, ohne dass man was machen muss.
Bei intensiver Nutzung und Spielereien ist mir allerdings auch schon aufgefallen, dass da wohl noch einige Bugs drin sind, vermutlich in iOS oder in Scriptable. Manchmal beginnen die Widgets zu blinken oder aktualisieren nicht mehr. Da hilft meistens ein Reboot des iPhones.
Hast du die ID oben im Skript angepasst? Denn da ist standardmäßig eine fest gesetzte drin (251), die von jener in den Widget Einstellungen überschrieben wird. Das passiert allerdings nicht, wenn du das Skript manuell in der App ausführst. Worauf ich hinaus will: vergleichst du die selben Shops?
Prinzipiell aktualisiert sich das Widget alle 5-10 Minuten von alleine, ohne dass man was machen muss.
Bei intensiver Nutzung und Spielereien ist mir allerdings auch schon aufgefallen, dass da wohl noch einige Bugs drin sind, vermutlich in iOS oder in Scriptable. Manchmal beginnen die Widgets zu blinken oder aktualisieren nicht mehr. Da hilft meistens ein Reboot des iPhones.
Ah, na klar. Stupid me.
Mich hat nur gewundert, warum sich die Zahlen überhaupt nicht verändern aber natürlich hat DM ja auch seine Aktualisierungsintervalle. Hab mein iPhone neu gestartet und jetzt funzt alles wieder prima. Besten Dank! 👍🏻
Erstmal Dankeschön für das (wie ich finde) nützliche Widget.
Ich wollte im Skript mal das PNG für das Mehl austauschen, hat aber auch nach korrekter Eingabe der img/URL nicht funktioniert.
Was mache ich hier falsch, oder geht das nicht?
Vielen Dank für das coole Widget. Wie findet man den die IDs für die Produkte raus? Hast du das einfach ausprobiert oder gibt es da einen Weg diese bei dm zu finden, ähnlich der Store ID? Gruß, Fabian!
Erstmal Dankeschön für das (wie ich finde) nützliche Widget.
Ich wollte im Skript mal das PNG für das Mehl austauschen, hat aber auch nach korrekter Eingabe der img/URL nicht funktioniert.
Was mache ich hier falsch, oder geht das nicht?
Ich hatte das schon auf englisch beantwortet und aus Faulheit mach ich mal copy/paste. ;)
I built an image cache which works like this:
If an image named 'toilet-paper.png' is already present in the local file store of the Scriptable app, this image will be loaded (locally). If it is not available, it will be downloaded once from the internet (using the given url). This happens usually the first time you run the script (or your widget triggers it).
There are different possibilities to change this behaviour:
- remove the if/else condition which checks whether the image is already available temporarily by uncommenting Line 224-226 and 245
- write a delete function and remove the three files from the store (once), like this:
let fm = FileManager.local()
let dir = fm.documentsDirectory()
let path = fm.joinPath(dir, 'toilet-paper.png')
fm.remove(path)
Vielen Dank für das coole Widget. Wie findet man den die IDs für die Produkte raus? Hast du das einfach ausprobiert oder gibt es da einen Weg diese bei dm zu finden, ähnlich der Store ID? Gruß, Fabian!
Im Browser die Developer Tools öffnen, dort den Netzwerk Tab und dort dann schauen was aufgerufen wird, wenn du z.B. im Shop auf "Toilettenpapier" klickst. Dieser Call hier liefert dann alle Ergebnisse, inklusive der dan Nummern:
https://products.dm.de/product/de/search?productQuery=%3Arelevance%3AallCategories%3A060201&hideFacets=true&hideSorts=true&pageSize=60
@marco79cgn:
Ist es auch möglich Produkte abzufragen, die nur online verfügbar sind? Ich warte händeringend auf Windeln und würde dein Script dafür benutzen ;-)
@marco79cgn:
Ist es auch möglich Produkte abzufragen, die nur online verfügbar sind? Ich warte händeringend auf Windeln und würde dein Script dafür benutzen ;-)
Ja, das ist möglich. Allerdings gibt es für die Online-Verfügbarkeit keine Stückzahl, sondern nur die Information "vorrätig oder nicht". Ich habe das im FFP-Masken Skript eingebaut:
https://gist.github.com/marco79cgn/c3410c8ecc8cb0e9f87409cee7b87338#gistcomment-3592366
Geht es um eine konkrete Marke/Sorte Windeln oder um mehrere?
Danke für die fixe Antwort! ❤
Geht um eine konkrete Marke. Ökowindeln
Apropos Windeln. Ich hab mal von dir kopiert und dieses Skript für die dm Ökowindeln angepasst. ;-)
https://gist.github.com/dakira/f3c7a138b0fac1212f832c103e10e0db
Seit ein paar Tagen wird für die Abfrage zum Toilettenpapier nur noch „NaN“ im Widget ausgegeben. Jemand eine Idee?
Seit ein paar Tagen wird für die Abfrage zum Toilettenpapier nur noch „NaN“ im Widget ausgegeben. Jemand eine Idee?
Hallo
Ändere die Zeile 161 so:
url = 'https://products.dm.de/store-availability/DE/availability?dans=595420,708997,137425,28171,485698,799358,452740,610544,709006,452753,485695,853483,525943,535981,127048&storeNumbers=' + storeId
Da sind wohl ein paar Artikelnummern nicht mehr gültig, und das Script kommt mit der Fehlermeldung nicht klar …
Mit freundlichen Grüßen Jochen
Hallo Jochen,
das klappt leider auch nicht (mehr).
Gruß
Hans
Hallo Hans
Bei mir klappt es noch mit der Zeile ...
Mit freundlichen Grüßen Jochen
Ich habe die storeID 2654 eingestellt, vielleicht liegt es daran.
Edit: Gerade festgestellt: deine Zeile bezieht sich auf Klopapier, bei mir wird aber bei Mehl NaN angezeigt.
Mit der wie folgt geänderten Zeile 191 funktioniert es bei mir wieder.
url = 'https://products.dm.de/store-availability/DE/availability?dans=468120,531500,468121,459912,468178,897927,461923,467198,755784,468119,721676&storeNumbers=' + storeId
Hallo
Zeile 189 ist bei mir noch original wie oben im Listing:
url = 'https://products.dm.de/store-availability/DE/availability?dans=488334,468120,706590,531500,468121,459912,468178&storeNumbers=' + storeId
Damit funktioniert es auch noch ...
Mit freundlichen Grüßen
Jochen
Das Widget funktioniert nicht mehr, da die Daten das falsche Format haben. DM scheint was geändert zu haben.
Jetzt geht es zwar wieder, zeigt aber bei Klopapier und Mehl jeweils „0“ an.
In den Zeilen 161 und 189 sind jeweils die ganzen Produktnummern für Klopapier und Mehl. Die haben aber einen Stand von Oktober 2020. Man müsste sich mal die Mühe machen und alle aktuellen Produktnummern zusammen suchen.
ok, ich habe mal ein paar aktuelle Produktnummern rausgesucht und eingesetzt. Es wird aber immer noch "0" angezeigt.
Allerdings scheint auch die Store ID eine andere zu sein, obwohl die Adresse richtig angezeigt wird. Statt 2654 steht in der URL jetzt d4ee und das kann das Script nicht handlen.
Wer es einmal ausprobieren möchte, die Artikelnummern in der Zeile 161 sind:
975799,877955,949772,799358,952683,952170,951954,870886,863567,985619,535981,879536,856834,853483,919184,127048,836029,777180,836029,871279,593761,137425,593802,876541
und in Zeile 189:
459912,755784,468178,468120,468168,721676,531500,849142
Rausgesucht auf der DM Seite, geht aber leider nicht, Summe ist 0.
Ich habe gerade eben das Skript oben angepasst, für Deutschland funktioniert es wieder. Österreich muss ich mir noch anschauen, vor allem was die Produktnummern angeht.
Die API von dm hat sich geändert bzw. die Endpunkte. Sowohl die Store Informationen als auch die Verfügbarkeits-URLs. Zudem haben sich offenbar alle Store IDs geändert. Bitte neu ermitteln auf dieser Seite.
Sieht super aus! Ich habe eine Android Version entwickelt. Hier ist mein Repository. Wer da auch reinschauen (und hoffentlich auch beitragen ;) ) will, ist herzlich willkommen!