Skip to content

Instantly share code, notes, and snippets.

@marco79cgn
Last active September 3, 2024 20:47
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
@Stefan-1982
Copy link

Wäre es auch möglich das Script so umzubauen das es nur einzelne Packungen auflistet? Fände es besser wenn ich weiß es sind noch 10 Packungen im Laden als 300 einzelne Rollen...

@marco79cgn
Copy link
Author

Wäre es auch möglich das Script so umzubauen das es nur einzelne Packungen auflistet? Fände es besser wenn ich weiß es sind noch 10 Packungen im Laden als 300 einzelne Rollen...

Das ist ein Missverständnis. Es sind bereits jetzt Packungen, also keine einzelnen Rollen. Es wird allerdings nicht unterschieden, ob es sich um 2er, 8er, 10er oder gar 16er Packungen handelt.

@utrenkler
Copy link

Versuche gerade das Widget nachzubauen um es in eine normale Webseite (Firefox etc.) einzubetten.
Leider kommt bei Abfrage der Produktverfügbarkeit mittels Fetch-API ein CORS-Fehler:

[Error] Preflight response is not successful
[Error] Fetch API cannot load 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=251 due to access control checks.

Hat zufällig jemand eine Idee?

@SoftCreatR
Copy link

Du testest das nicht zufällig lokal?

@JDTm
Copy link

JDTm commented Jan 12, 2021

Hat schon jemand die FFP2 Masken Codes rausgesucht?

@marco79cgn
Copy link
Author

Hat schon jemand die FFP2 Masken Codes rausgesucht?

B54448E0-2EA1-47C4-AE70-4F43A2EE1E39

@JDTm
Copy link

JDTm commented Jan 12, 2021

Hat schon jemand die FFP2 Masken Codes rausgesucht?

B54448E0-2EA1-47C4-AE70-4F43A2EE1E39

Cool! Hast du einen Link dazu?

@marco79cgn
Copy link
Author

Cool! Hast du einen Link dazu?

Braucht noch etwas Feinschliff, kommt morgen.

@JDTm
Copy link

JDTm commented Jan 13, 2021

Cool! Hast du einen Link dazu?

Braucht noch etwas Feinschliff, kommt morgen.

wunderbar! Dann gute Nacht! Mein Kollege hat sich heute die sinkenden Bestände in München & Umgebung beobachtet.

@marco79cgn
Copy link
Author

@drkWin78
Copy link

Hallo zusammen... Die Widgets sind richtig gut 👌🏻Ich habe eine Frage bzw. Bitte. Ich nutze den Dark Mode, würde mir die Widgets aber gerne in der Light Mode Version anzeigen lassen. Ist das möglich?

Kann lediglich über einfügen widget.backround etc. die Hintergrundfarbe ändern. Die Schriftfarbe jedoch nicht.

Kann man den automatischen Switch Dark/Light ändern? Bzw. abschalten?

Vielen Dank
Dirk

@marco79cgn
Copy link
Author

Hallo zusammen... Die Widgets sind richtig gut 👌🏻Ich habe eine Frage bzw. Bitte. Ich nutze den Dark Mode, würde mir die Widgets aber gerne in der Light Mode Version anzeigen lassen. Ist das möglich?

Kann lediglich über einfügen widget.backround etc. die Hintergrundfarbe ändern. Die Schriftfarbe jedoch nicht.

Kann man den automatischen Switch Dark/Light ändern? Bzw. abschalten?

Vielen Dank
Dirk

Doch genau, du müsstest den Hintergrund fix auf weiß setzen und sämtliche Texte auf schwarz. Das geht zum Beispiel so:

const blaText = widget.addText("Blubb")
blaText.textColor = Color.black()

Wenn man die Textfarbe gar nicht setzt, dann wird automatisch im Light Mode schwarz genommen und im Dark Mode weiß.

@drkWin78
Copy link

Hallo zusammen... Die Widgets sind richtig gut 👌🏻Ich habe eine Frage bzw. Bitte. Ich nutze den Dark Mode, würde mir die Widgets aber gerne in der Light Mode Version anzeigen lassen. Ist das möglich?
Kann lediglich über einfügen widget.backround etc. die Hintergrundfarbe ändern. Die Schriftfarbe jedoch nicht.
Kann man den automatischen Switch Dark/Light ändern? Bzw. abschalten?
Vielen Dank
Dirk

Doch genau, du müsstest den Hintergrund fix auf weiß setzen und sämtliche Texte auf schwarz. Das geht zum Beispiel so:

const blaText = widget.addText("Blubb")
blaText.textColor = Color.black()

Wenn man die Textfarbe gar nicht setzt, dann wird automatisch im Light Mode schwarz genommen und im Dark Mode weiß.

Ok. Danke für die Rückmeldung! 🙋🏼‍♂️Werde es versuchen...

@drkWin78
Copy link

const blaText = widget.addText("Blubb")
blaText.textColor = Color.black()

Ich habe es fast hinbekommen! 😬Nur leider beim Ort weiß ich nicht was ich eingeben soll:

// Main stack for value and area name
let incidenceStack = textStack.addStack()
let valueStack = incidenceStack.addStack()
incidenceStack.layoutVertically()
let incidenceValueLabel = valueStack.addText(data.incidence + data.trend)
incidenceValueLabel.font = Font.boldSystemFont(24)
incidenceValueLabel.textColor = data.incidence >= 100 ? new Color("9e000a") : data.incidence >= 50 ? Color.red() : data.incidence >= 35 ? Color.yellow() : Color.green();
incidenceStack.addText(data.areaName)

Der Ort wird weiterhin in weiß angezeigt und verschwindet natürlich beim fix setzen des Hintergrundes auf weiß 🤦🏼‍♂️🤷‍♂️

@marco79cgn
Copy link
Author

const blaText = widget.addText("Blubb")
blaText.textColor = Color.black()

Ich habe es fast hinbekommen! 😬Nur leider beim Ort weiß ich nicht was ich eingeben soll:
incidenceStack.addText(data.areaName)

Der Ort wird weiterhin in weiß angezeigt und verschwindet natürlich beim fix setzen des Hintergrundes auf weiß 🤦🏼‍♂️🤷‍♂️

Das ist nicht mein Widget. Probier es so. Statt
incidenceStack.addText(data.areaName)
das hier

const areaNameText = incidenceStack.addText(data.areaName)
areaNameText.textColor = Color.black()

@drkWin78
Copy link

const blaText = widget.addText("Blubb")
blaText.textColor = Color.black()

Ich habe es fast hinbekommen! 😬Nur leider beim Ort weiß ich nicht was ich eingeben soll:
incidenceStack.addText(data.areaName)
Der Ort wird weiterhin in weiß angezeigt und verschwindet natürlich beim fix setzen des Hintergrundes auf weiß 🤦🏼‍♂️🤷‍♂️

Das ist nicht mein Widget. Probier es so. Statt
incidenceStack.addText(data.areaName)
das hier

const areaNameText = incidenceStack.addText(data.areaName)
areaNameText.textColor = Color.black()

Krass 😳Das hat funktioniert! Vielen Dank 🙏🏼

@MatthiasSeifert1
Copy link

Herzlichen Dank für die Version mit Masken. Heute hingen im Verkaufsraum noch zwei Päckchen. Eins habe ich genommen. Ich weiß jetzt, dass im Lager noch mehrere da waren.

Könnte jemand ein Bild einbauen, das eine FFP2-Maske zeigt und nicht den Mundnasenschutz?
7A95BA99-0744-49B3-B5EC-5B6CA8405A55

@not-a-feature
Copy link

Stark inspiriert von dem Klopapier Widget hab ich ein Impftermin-Widget gebaut:

freie Termine keine Termine
Screenshot Freie Termine Screenshot keine freie Termine

Es zeigt an ob ihm lokalen Impfzentrum noch Termine verfügbar sind. Das ganze funktioniert aktuell in ["Baden-Württemberg", "Hamburg", "Hessen", "Nordrhein-Westfalen", "Sachsen-Anhalt"]

https://gist.github.com/not-a-feature/4e6dbbd9eb3bd927e50cae347b7e0486

@thomas-merz
Copy link

Sehr geil, @not-a-feature , so erspart man sich selber für seine Eltern/Großeltern und natürlich auch denselbigen viele unnötige und vor allem erfolglose Anrufe und elend lange Warteschlangen 👍

@SchneHa
Copy link

SchneHa commented Apr 20, 2021

Das Widget funktioniert leider nicht mehr. Ich bekomme anstatt der Anzahl nur noch „NaN“ angezeigt. Hat DM was an der Webseite geändert?

@marco79cgn
Copy link
Author

Das Impf-Widget hat nichts mit diesem Klopapier-Widget hier zu tun und verhindert wird bisher nichts bei dm. Da scheint sich an der Struktur des json was geändert zu haben. Schau ich mir an...

@JDTm
Copy link

JDTm commented Apr 20, 2021

Bisschen verwirrend: Also aktuell funktioniert weder das DM Widget als auch das Impf Widget.

@marco79cgn
Copy link
Author

Update (20.04.2021): Skript funktioniert wieder
Einige Produkte sind nicht mehr im Sortiment und daher hat der Counter nicht mehr funktioniert. Bitte Skript neu kopieren.

@JDTm
Copy link

JDTm commented Apr 20, 2021

das FFP2+Test Widget funktioniert auch nicht mehr. Dann hat DM ja heute viel rumgebastelt. Kannst du die Versionsnummer/Last Update noch ins Script mit schreiben. Danke

@marco79cgn
Copy link
Author

marco79cgn commented Apr 20, 2021

das FFP2+Test Widget funktioniert auch nicht mehr. Dann hat DM ja heute viel rumgebastelt.

Eigentlich hat dm gar nichts geändert. Man muss eben die Produktnummer im Skript aktuell halten. Bei so schnelllebigen Dingen wie Masken fliegen eben mal welche aus dem Sortiment und neue kommen hinzu. Zusätzlich war der Counter nicht robust, so dass ein ungültiges Produkt zu diesem NaN geführt hat. Ist jetzt auch im FFP Skript behoben.

Kannst du die Versionsnummer/Last Update noch ins Script mit schreiben.

Es ist sehr wenig Platz im Widget und ich persönlich finde diese Info nicht sehr sinnvoll. Aber du kannst sie gerne bei dir manuell ergänzen. Die Klopapier und FFP2 Skripte nutzen kein Caching (im Gegensatz zum Impffortschritt), so dass sie alle 5-10 Minuten aktualisiert werden. Daher ist Last Update imho auch eher unnötig.

@BooAbi83
Copy link

BooAbi83 commented Sep 6, 2021

Hallo,

kann ich das Skript auch für andere Produkte nutzen wenn ja wie genau?, ich würde gerne wissen ob im DM Babynahrung verfügbar ist?

Vielen Dank vorab.

@SoftCreatR
Copy link

Du brauchst zwei Dinge:

  1. IDs der Produkte, die du beobachten willst
  2. Eine Store-ID. Diese bekommst du zum Beispiel dort: https://softcreatr.github.io/dm-store-finder/

@BooAbi83
Copy link

BooAbi83 commented Sep 6, 2021

Du brauchst zwei Dinge:

  1. IDs der Produkte, die du beobachten willst
  2. Eine Store-ID. Diese bekommst du zum Beispiel dort: https://softcreatr.github.io/dm-store-finder/

Ok die Store-ID hab ich, wo füge ich die ID des Produktes ein?

@Aim23
Copy link

Aim23 commented Sep 6, 2021

Du brauchst zwei Dinge:

  1. IDs der Produkte, die du beobachten willst
  2. Eine Store-ID. Diese bekommst du zum Beispiel dort: https://softcreatr.github.io/dm-store-finder/

Ok die Store-ID hab ich, wo füge ich die ID des Produktes ein?

https://gist.github.com/marco79cgn/23ce08fd8711ee893a3be12d4543f2d2#file-dm-toilet-paper-js-L128

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