Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
iOS Widget, das die Anzahl an Klopapier und Mehl Packungen in deiner nächsten dm Drogerie anzeigt (für die scriptable.app)
// dm Klopapier & Mehl Widget
//
// Copyright (C) 2020 by marco79 <marco79cgn@gmail.com>
//
// 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 = 251
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/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
const req = new Request(url)
const apiResult = await req.loadJSON()
for (var i in apiResult.storeAvailabilities) {
counter += apiResult.storeAvailabilities[i][0].stockLevel
}
}
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/availability?dans=488334,468120,706590,531500,468121,459912,468178&storeNumbers=' + storeId
const req = new Request(url)
const apiResult = await req.loadJSON()
for (var i in apiResult.storeAvailabilities) {
counter += apiResult.storeAvailabilities[i][0].stockLevel
}
}
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/de/' + 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
@marco79cgn

This comment has been minimized.

Copy link
Owner Author

@marco79cgn marco79cgn commented Oct 25, 2020

Intro

Version 2.0 und Nachfolger des dm Klopapier Widgets: jetzt mit Klopapier und Mehl Auskunft.

Es zeigt die Vorräte an Toilettenpapier und Mehl in deiner nächsten dm Drogerie. 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.

Bekannt aus dem Netz: RTL, Macwelt, Business Punk, Berliner Zeitung, iPhone-ticker, t3n, Caschys Blog, RND, Giga, Computer Bild, Chip, Curved, Notebooksbilliger, Vowe, Billiger-Telefonieren u.a.

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.

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. DM-Klopapier)
  • Speicher 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ück 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 hinzugügen"
  • Drück 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 (DM-Klopapier)
  • gib als "Parameter" die ermittelten Store ID ein, z.B. 180 für Mainz

Thanks

Großer Dank an @simonbs für großartige Apps wie Scriptable, DataJar oder Jayson.

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 Klopapier.

@FM-500

This comment has been minimized.

Copy link

@FM-500 FM-500 commented Oct 29, 2020

Leider Error on line 242:40: ReferenceError: Can't find variable: loadImage

@marco79cgn

This comment has been minimized.

Copy link
Owner Author

@marco79cgn marco79cgn commented Oct 29, 2020

Leider Error on line 242:40: ReferenceError: Can't find variable: loadImage

Bitte bis zum Ende (ganz unten) alles markieren. Dann klappt es.
Pro-Tipp: erst in Safari ganz nach unten scrollen (in der Raw-Ansicht) und dann von unten nach oben markieren.

@FM-500

This comment has been minimized.

Copy link

@FM-500 FM-500 commented Oct 29, 2020

Leider Error on line 242:40: ReferenceError: Can't find variable: loadImage

Bitte bis zum Ende (ganz unten) alles markieren. Dann klappt es.
Pro-Tipp: erst in Safari ganz nach unten scrollen (in der Raw-Ansicht) und dann von unten nach oben markieren.

Astrein, danke!

@AndresAnariba

This comment has been minimized.

Copy link

@AndresAnariba AndresAnariba commented Oct 29, 2020

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!

@Jaxor84

This comment has been minimized.

Copy link

@Jaxor84 Jaxor84 commented Oct 30, 2020

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!

@marco79cgn

This comment has been minimized.

Copy link
Owner Author

@marco79cgn marco79cgn commented Oct 30, 2020

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.

@Jaxor84

This comment has been minimized.

Copy link

@Jaxor84 Jaxor84 commented Oct 30, 2020

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! 👍🏻

@juergen2812

This comment has been minimized.

Copy link

@juergen2812 juergen2812 commented Oct 31, 2020

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?

@WerderFF

This comment has been minimized.

Copy link

@WerderFF WerderFF commented Nov 4, 2020

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!

@marco79cgn

This comment has been minimized.

Copy link
Owner Author

@marco79cgn marco79cgn commented Nov 5, 2020

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)
@marco79cgn

This comment has been minimized.

Copy link
Owner Author

@marco79cgn marco79cgn commented Nov 5, 2020

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
@elmocito

This comment has been minimized.

Copy link

@elmocito elmocito commented Jan 19, 2021

@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

This comment has been minimized.

Copy link
Owner Author

@marco79cgn marco79cgn commented Jan 19, 2021

@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?

@elmocito

This comment has been minimized.

Copy link

@elmocito elmocito commented Jan 19, 2021

Danke für die fixe Antwort!
Geht um eine konkrete Marke. Ökowindeln

@dakira

This comment has been minimized.

Copy link

@dakira dakira commented Mar 12, 2021

Apropos Windeln. Ich hab mal von dir kopiert und dieses Skript für die dm Ökowindeln angepasst. ;-)

https://gist.github.com/dakira/f3c7a138b0fac1212f832c103e10e0db

@cyc68

This comment has been minimized.

Copy link

@cyc68 cyc68 commented Apr 21, 2021

Seit ein paar Tagen wird für die Abfrage zum Toilettenpapier nur noch „NaN“ im Widget ausgegeben. Jemand eine Idee?

@jochenpa

This comment has been minimized.

Copy link

@jochenpa jochenpa commented Apr 28, 2021

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

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