Skip to content

Instantly share code, notes, and snippets.

@marco79cgn
Last active February 29, 2024 17:35
Show Gist options
  • Save marco79cgn/23ce08fd8711ee893a3be12d4543f2d2 to your computer and use it in GitHub Desktop.
Save marco79cgn/23ce08fd8711ee893a3be12d4543f2d2 to your computer and use it in GitHub Desktop.
iOS Widget, das die Anzahl an Klopapier Packungen in deiner nächsten dm Drogerie anzeigt (für die scriptable.app)
// dm Klopapier 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.
//
// Toilet paper icon made by boettges
let country = 'de' // für Österreich bitte 'at' verwenden
let storeId = 251
let param = args.widgetParameter
if (param != null && param.length > 0) {
storeId = param
}
const widget = new ListWidget()
const storeInfo = await fetchStoreInformation()
const storeCapacity = await fetchAmountOfPaper()
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() {
widget.addSpacer(4)
const logoImg = await getImage('dm-logo.png')
widget.setPadding(10, 10, 10, 10)
const titleFontSize = 12
const detailFontSize = 36
const logoStack = widget.addStack()
logoStack.addSpacer(86)
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(40, 40)
wimg.rightAlignImage()
widget.addSpacer()
const icon = await getImage('toilet-paper.png')
let row = widget.addStack()
row.layoutHorizontally()
row.addSpacer(2)
const iconImg = row.addImage(icon)
iconImg.imageSize = new Size(40, 40)
row.addSpacer(13)
let column = row.addStack()
column.layoutVertically()
const paperText = column.addText("KLOPAPIER")
paperText.font = Font.mediumRoundedSystemFont(13)
const packageCount = column.addText(storeCapacity.toString())
packageCount.font = Font.mediumRoundedSystemFont(22)
if (storeCapacity < 30) {
packageCount.textColor = new Color("#E50000")
} else {
packageCount.textColor = new Color("#00CD66")
}
widget.addSpacer(4)
const row2 = widget.addStack()
row2.layoutVertically()
const street = row2.addText(storeInfo.address.street)
street.font = Font.regularSystemFont(11)
const zipCity = row2.addText(storeInfo.address.zip + " " + storeInfo.address.city)
zipCity.font = Font.regularSystemFont(11)
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.mediumSystemFont(11)
}
// 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) {
if (apiResult.storeAvailabilities[i][0].stockLevel) {
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
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 kopieren
@noelkligonis
Copy link

Hallo, vielen Dank für das Script! Ich habe eben mal versucht, es etwas anzupassen für ein anderes Produkt. Da es dort nicht geklappt hat, habe ich mir zur Kontrolle noch einmal eine einzelne Produkt-ID von einem in meiner Filiale lt website verfügbaren Klopapiers genommen, aber der counter spuckt dennoch 0 aus. Hat sich ggf im Stocklevel check seitens dm was verändert? Wenn ich den request im browser ausführe, kommt bspw folgender output:

{"tenant":"de","storeAvailabilities":{"4010355274656":[{"store":{"storeNumber":"1099"},"dan":"4010355274656","inStock":false,"stockLevel":0,"predictedStockLevel":0,"checkStatus":{"status":"ERROR","code":"STOCK_LEVEL_CHECK_FAILED"}}]}}

Was kann ich tun, um die Abfrage mit Erfolg ins Script zu kriegen? Und wäre es auf lange Sicht denkbar, dass die Store ID automatisch anhand des per Standort ermittelten DM Stores eingetragen wird? LG

@marco79cgn
Copy link
Author

marco79cgn commented Nov 23, 2022

Habe eben mal schnell im Browser drauf geschaut und an der Abfrage bezüglich des Stock-Levels hat sich nichts geändert.
Beispiel: Zewa Toilettenpapier Smart 3-lagig (4x300 Blatt), 4 St
URL: https://www.dm.de/zewa-toilettenpapier-smart-3-lagig-4-x-300-blatt-p7322541147741.html
dan: 853483
Api call: https://products.dm.de/store-availability/de/availability?dans=853483&storeNumbers=300
Ergebnis:

{
    "tenant": "de",
    "storeAvailabilities": {
        "853483": [
            {
                "store": {
                    "storeNumber": "300"
                },
                "dan": "853483",
                "inStock": true,
                "stockLevel": 12,
                "checkStatus": {
                    "status": "OK",
                    "code": "CHECK_SUCCESSFUL"
                }
            }
        ]
    }
}

Deine dan sieht allerdings seltsam aus, sehr lang. Um welches Produkt handelt es sich denn? Und auf welches Produkt wolltest du es anpassen?

@noelkligonis
Copy link

Habe eben mal schnell im Browser drauf geschaut und an der Abfrage bezüglich des Stock-Levels hat sich nichts geändert. Beispiel: Zewa Toilettenpapier Smart 3-lagig (4x300 Blatt), 4 St URL: https://www.dm.de/zewa-toilettenpapier-smart-3-lagig-4-x-300-blatt-p7322541147741.html dan: 853483 Api call: https://products.dm.de/store-availability/de/availability?dans=853483&storeNumbers=300 Ergebnis:

{
    "tenant": "de",
    "storeAvailabilities": {
        "853483": [
            {
                "store": {
                    "storeNumber": "300"
                },
                "dan": "853483",
                "inStock": true,
                "stockLevel": 12,
                "checkStatus": {
                    "status": "OK",
                    "code": "CHECK_SUCCESSFUL"
                }
            }
        ]
    }
}

Deine dan sieht allerdings seltsam aus, sehr lang. Um welches Produkt handelt es sich denn? Und auf welches Produkt wolltest du es anpassen?

Hi, danke für die Antwort. Habe nochmal nachgesehen, es lag an der DAN… die hab ich falsch gesourced. Habe die aus der url einer Produktansicht genommen und das war der Fehler..

@marco79cgn
Copy link
Author

Ich habe einen Siri Shortcut gebaut, mit dem man anhand der PLZ eine Store ID ermitteln kann und anschließend anhand des Produktnamens die passende DAN finden.

Shortcut Download: click

@GordonQuick
Copy link

GordonQuick commented Mar 28, 2023

Jetzt mal ganz doof gefragt, was kann man machen wenn der Availabilitycheck eines Artikels nur den Error ausspuckt? Einfach abwarten und hoffen dass es von DM wieder gefixt wird? Meist ist der Artikel nicht vorhanden aber dann wurde in der Vergangenheit immer brav "0" angezeigt. In der regulären Kartenansicht steht auch "Keine Anzeige möglich". Oder macht es DM vielleicht sogar extra bei Dingen die schnell ausverkauft sind?

Übrigens Riesendank für die ganzen Widgets, ich hab die wahrscheinlich auf die amateurhafteste Weise umgebaut aber für einige Zeit haben sie fantastisch funktioniert bis die Abfrage mit Fehlermeldungen angefangen hat.

@SchneHa
Copy link

SchneHa commented Mar 29, 2023 via email

@73mates
Copy link

73mates commented Aug 12, 2023

Hey man, would it be possible to make it work in Czech republic? I tried, but I couldn’t make it work :(.
Thanks a lot for your reply!
M.

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