// 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) { | |
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 |
This comment has been minimized.
This comment has been minimized.
Sehr schöne Idee, sehr schön umgesetzt! |
This comment has been minimized.
This comment has been minimized.
Sehr geil! Vielleicht noch wie im Inzidenzen Script für die Dark Mode Nutzer folgendes einbauen.
|
This comment has been minimized.
This comment has been minimized.
Ich finde das sehr geil! Jetzt installiert |
This comment has been minimized.
This comment has been minimized.
Wirklich tolle Idee mit Liebe zum Detail |
This comment has been minimized.
This comment has been minimized.
Tolles script, wie oschni schon schreibt wäre die Anpassung an den darkmode wünschenswert. Unser Dm wir um 10:30 Uhr als geschlossen angezeigt. ID 1589 |
This comment has been minimized.
This comment has been minimized.
Hier ne Version inklusive dark mode fix. |
This comment has been minimized.
This comment has been minimized.
Großartig! Vielen lieben Dank! |
This comment has been minimized.
This comment has been minimized.
Wirklich cool! Gibt es für die DM-Tech API eine Doku oder hast du dir die Request zusammengesucht? |
This comment has been minimized.
This comment has been minimized.
Wie komme ich an die DM ID? |
This comment has been minimized.
This comment has been minimized.
Ist oben in den Requirements beschrieben! |
This comment has been minimized.
This comment has been minimized.
Danke, habe ich oben so ergänzt. Nutze tatsächlich nie den Dark Mode. Ich schau mal, ob ich dafür ein helleres dm Logo finde. |
This comment has been minimized.
This comment has been minimized.
Ich habe mir das über die Developer Tools im Browser hergeleitet. Der Shop macht das gleiche, wenn du dort surfst/kaufst. Die IDs der Produkte habe ich über eine Suche bekommen. |
This comment has been minimized.
This comment has been minimized.
Sowohl der kleine Bug bezüglich der Öffnungszeiten als auch der Dark Mode sind inzwischen korrigiert. Einfach nochmal neu kopieren. :) |
This comment has been minimized.
This comment has been minimized.
Sehr schön, aber leider werden alle Artikel die unter "Toilettenpapier" im Suchergebnis bei DM gelistet sind aufgeführt, also auch feuchtes Toilettenpapier, was ja nicht die Mangelware ist sondern das normale Klopapier. Kannst du da noch was optimieren? Ansonsten tolles und witziges Widget |
This comment has been minimized.
This comment has been minimized.
Hey @marco79cgn - super coole Idee! Ich habe das Inzidenz Widget gebaut. Edit: Und Glückwunsch zum nächsten Social-Media-Hype :-). |
This comment has been minimized.
This comment has been minimized.
Super Idee! Leider kann ich nicht (gut genug) programmieren, kann das eventuell jemand für die Auslastungsanzeige von FitX Sportstudios umsetzen? Z. B. https://www.fitx.de/fitnessstudios/gelsenkirchen-hessler Wäre super! |
This comment has been minimized.
This comment has been minimized.
wie kommt man an die IDs ... hab es nicht herausgefunden?!? |
This comment has been minimized.
This comment has been minimized.
Das Widget selbst beachtet keine feuchten Toilettenpapiere etc. Ich habe extra alle IDs der korrekten Produkte rausgesucht. Nur wenn du den Link öffnest, landest du auf der dm Seite und dort sind dann auch die ganzen Feuchttücher etc. |
This comment has been minimized.
This comment has been minimized.
Danke! :) |
This comment has been minimized.
This comment has been minimized.
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: |
This comment has been minimized.
This comment has been minimized.
Chapeau 👏🏼 Sehr gute Umsetzung und (leider) sehr geile Idee! |
This comment has been minimized.
This comment has been minimized.
Mich köstlich amüsiert. Aber schon auch etwas traurig, dass man Egoismus damit auch noch unterstützt. Dennoch echt witzige Idee. |
This comment has been minimized.
This comment has been minimized.
Kannst du dir mal Markt 1279 anschauen? Da fehlen bei mir einige Informationen.... |
This comment has been minimized.
This comment has been minimized.
Bei mir wird im Widget nur der Scriptable Hintergrund (Play Symbol mit verschiedenen Icons) angezeigt - das Ausführen in Scriptable funktioniert und zeigt auch das Widget an. Das Widget ist auf den Script-Namen, "Run Script" und den Parameter der DM Filiale gesetzt. Muss man die Ausführung von Widgets in iOS oder Scriptable freischalten? |
This comment has been minimized.
This comment has been minimized.
Super Idee und Umsetzung! |
This comment has been minimized.
This comment has been minimized.
Hammer gutes Projekt und sehr witzig |
This comment has been minimized.
This comment has been minimized.
Mega geil. Bin mal gespannt wo die Reise mit solchen Widgets hingeht. Lässt sich das auch für andere Artikel bzw andere Geschäfte realisieren? ALDI, LIDL? Push Meldung wenn ein Artikel ausverkauft war und wieder verfügbar sein sollte wäre noch interessant. Bin gespannt auf weiter Ideen uns Scripts von dir @marco79cgn |
This comment has been minimized.
This comment has been minimized.
Ich habe meinen Geschäft im Widget immer geschlossen gesehen. Aber hier ist meine Lösung.
Jetzt möchte ich etwas für Budni... |
This comment has been minimized.
This comment has been minimized.
Das sollte eigentlich direkt funktionieren. Es gibt leider ein paar Bugs, die mit iOS 14.1 wieder gekommen sind. Im Zweifel mal rebooten, das Widget komplett löschen und von vorne beginnen. |
This comment has been minimized.
This comment has been minimized.
Ich habe analysiert, was im Browser passiert, wenn man dort shoppt. Schau mal hier. Der Call liefert alle Produkte mit IDs und Beschreibung. Daraus habe ich die IDs gefiltert, die mich interessieren (also kein Feuchtpapier zum Beispiel). |
This comment has been minimized.
This comment has been minimized.
Danke! Bei ALDI oder LIDL ist mir nicht bekannt, dass sie eine Auskunft haben über ihr Sortiment. Für andere Produkte von dm ginge das natürlich. |
This comment has been minimized.
This comment has been minimized.
Da werden mir dann aber keine namen sondern nur Verfügbarkeiten von bestimmten Nummern angezeigt, wenn ich den Link öffne... |
This comment has been minimized.
This comment has been minimized.
Ziemlich cool, funktioniert auch reibungslos! Allerdings schaff ich es nicht, dasselbe für österreichische Filialen hinzubekommen (wohne in Wien, da macht das Sinn). Die store-IDs sind leicht zu bekommen (bei store-ID 170 komm' ich z.B. oft vorbei), was sonst noch zu änderen wäre, ist mir ein Rätsel. Ich bin dankbar über jeden Tipp :) |
This comment has been minimized.
This comment has been minimized.
Für iPhone 7 passt KLOPAPIER nicht mehr ganz in das Widget.
|
This comment has been minimized.
This comment has been minimized.
Update: Österreich wird jetzt auch unterstützt. Ist etwas aufwendiger, da man jeden Artikel einzeln aufrufen muss. Aber klappt! |
This comment has been minimized.
This comment has been minimized.
Danke für das Feedback, ist jetzt angepasst. |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Mich interessierte Mehl auch mehr als Klopapier, habe den Code als Fork online gestellt: https://gist.github.com/achisto/ebccd305226218f1cbd01540213403c3 |
This comment has been minimized.
This comment has been minimized.
Geniale Idee! Vielleicht mit verschiedenen Produkten und Store Suche. Wär ein cooles Projekt denke ich und auch super gefragt außerhalb der Tech-Bubble. |
This comment has been minimized.
This comment has been minimized.
@achisto du hast die Hefe vergessen ;-) |
This comment has been minimized.
This comment has been minimized.
Hi Marco, Unter: Ich habe für mich erstmal alle Artikel drin belassen und per Abfrage nachgeschaut ob diese Verfügbar sind. Alle Über eine weitere Abfrage dann alle Für mich das erste mal das ich in Flask gearbeitet habe. Die Flask App kann dann z.B. auf einem Raspberry Pi, AWS oder anderswo online bzw. lokal laufen. Da dein Script auf Caschys Blog ein wenig eingeschlagen hat und die Leute nach einer Android Lösung gesucht haben, habe ich die Lösung mittels Flask gewählt. Über Tasker lässt sich dann einfach ein HTTP GET Request ausführen, der dann weiter verarbeitet werden kann. Beispiel Bilder sind hier https://imgur.com/a/qgqjyxD zu finden. |
This comment has been minimized.
This comment has been minimized.
@DSchumacher2104 Ich schaus mir Mal an. Hab schon eins fürs McFit geschrieben ;) |
This comment has been minimized.
This comment has been minimized.
Und an Marco, danke für das super Widget! :D Endlich Mal was, um meinen hässlichen Homescreen mit bissl Klopapier aufzuhübschen. ^^ |
This comment has been minimized.
This comment has been minimized.
@masselmello Cool, leider bin ich dort kein Mitglied |
This comment has been minimized.
This comment has been minimized.
Schau dir den Gist mal an: (vorerst entfernt) |
This comment has been minimized.
This comment has been minimized.
Keine schlechte Idee, aber das ist eigentlich die Aufgabe der dm Entwicklungsabteilung. Sie nahmen dieses widget hier mit Humor (lucky me), aber eine App ginge sicherlich etwas zu weit. Zumal sie die api auch problemlos absichern könnten, so dass man nicht mehr ohne weiteres darauf zugreifen könnte. Abgesehen davon gibt der Online-Shop sowie deren eigene App diese Funktion ja schon mehr oder weniger her. Also eine Bestandsanzeige für Artikel in einem beliebigen Shop. Und eigentlich war das alles als reiner Gag gedacht. Aber der Monk in mir wollte es eben funktional haben, nicht mit fake Daten. Freut mich natürlich, was das für ein Feedback gab! Vielen Dank an dieser Stelle an alle für die (größtenteils) positiven Reaktionen und natürlich für die ganzen Stars hier bei Github. Leider kann ich mangels Android Smartphone keine Portierung machen. Mir wäre auch keine Android App bekannt, mit der man auf solch einfache Weise content auf den Homescreen bringen kann. Scriptable ist diesbezüglich schlicht genial. |
This comment has been minimized.
This comment has been minimized.
Beim Inzidenz-Widget hatte ein User das bereits für Android portiert. Hier der Link zum Fork. Vielleicht auch hier möglich? Habe leider auch keinen Androiden daheim, sonst würde ich das testen. |
This comment has been minimized.
This comment has been minimized.
Da offensichtlich Bedarf besteht: |
This comment has been minimized.
This comment has been minimized.
Sneak Preview 2.0: Gute Idee oder zu viel des Guten? |
This comment has been minimized.
This comment has been minimized.
Cool! Wie ist das mit Fitstar? https://www.fit-star.de/ |
This comment has been minimized.
This comment has been minimized.
Ich bekomme grad eine Fehlermeldunfg: Error on Line 78:65: TypeError: undefined is not an obejct (evaluating 'storeInfo.openinghours[currentDay]timeRanges'). |
This comment has been minimized.
This comment has been minimized.
Sorry, da war ein fieser Bug drin, den ich bereits behoben habe. Bitte das Skript nochmal neu kopieren. Ganz oben im ersten Kommentar steht immer, wenn es Updates gibt. Vielleicht überführe ich das mal in ein Github Projekt mit ReadMe. |
This comment has been minimized.
This comment has been minimized.
Ich dachte eher im Script: Script ruft zusätzlich noch Webseite X auf und prüft ob es eine neue Version gibt und zeigt dann eine rote Ecke im Widget? |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
vielen dank! bin begeistert! |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Ich möchte anregen, den query von “toilettenpapier” zu “toilettenpapier%20rolle” anzupassen. Das knappe gut sind ja die Rollen und nicht die feuchten Waschlappen. |
This comment has been minimized.
This comment has been minimized.
Ich würde es großartig finden, wenn ich mir die verschiedenen Mehlsorten und -typen im |
This comment has been minimized.
This comment has been minimized.
Gute Idee, das ist schon mehreren passiert. Ich habe noch eine Leerzeile und einen Kommentar eingefügt am Ende. Wenn man den nicht kopiert, passiert zumindest kein Fehler. :) |
This comment has been minimized.
This comment has been minimized.
@eopo Super, vielen Dank |
This comment has been minimized.
This comment has been minimized.
Ich habe nur die Artikelnummern (dans) von "echten" Klopapierrollen rausgesucht. Feuchttücher werden bei der Anzahl im Widget nicht berücksichtigt. Lediglich wenn man drauf tippt und dann in den dm Shop geleitet wird, werden alle Produkte angezeigt. |
This comment has been minimized.
This comment has been minimized.
@marco79cgn Ich finde die Idee für V2.0 super, allerdings wäre es tatsächlich optimal, wenn man auch einzelne Mehlsorten auswählen könnte |
This comment has been minimized.
This comment has been minimized.
@boettges Warum hast du dein Script rausgenommen? |
This comment has been minimized.
This comment has been minimized.
Zum Thema Mehl: |
This comment has been minimized.
This comment has been minimized.
Ein kleiner Vorschlag zu dem Fix für die index out of bounds exception die auch für verkaufsoffene Sonntage funktioniert:
wohingegen bei
die dm-stores im widget Sonntags immer geschlossen bleiben. |
This comment has been minimized.
This comment has been minimized.
könnte man das Ganze nicht etwas generischer machen und neben der ShopID auch die ProductID, den Query, den Produktname der angezeigt werden soll und die URL zum png als Parameter übergeben? Ich hab mir das gestern auf Balea Flüssigseife umgebaut und heute musste ich die Änderungen alle neu da reinbasteln. Sneak Preview 2.0 ist natürlich auch sehr geil :D |
This comment has been minimized.
This comment has been minimized.
Viel besser, Danke! An verkaufsoffene Sonntage habe ich gar nicht gedacht. Werde ich so einbauen. An dieser Stelle Sorry für meine überschaubaren Javascript skills. Da bin ich nicht wirklich zuhause. Echte Javascript geeks würden den Code sicher anders schreiben, mit lambdas, promises, fancy pancy etc. Meine Variablennamen sich sicher auch viel zu lang (→ mehr als ein Buchstabe)... ;) |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Klar könnte man das. Aber dann wird es (m.M.n.) viel fehleranfälliger und "Otto-Normal-Verbraucher" wäre damit vermutlich ziemlich überfordert. Ich mag das Skript am liebsten so, dass man gar nichts editieren muss. Die Lösung für Österreich gefällt mir schon nicht wirklich, hatte ich erst anders geplant. Aber leider gibt es Überschneidungen bei den store ids, sie sind also nicht eindeutig (nur eindeutig pro Land). |
This comment has been minimized.
This comment has been minimized.
Für dich ist Mehl vielleicht spezieller. Andere haben auch bei Klopapier ihre ganz klaren Vorlieben (ich nutze z.B. ausschließlich Recycling-Papier). Wenn es im Notfall hart auf hart kommt ist man sicherlich nicht mehr so wählerisch. Und darum ging es doch letztendlich. ;) |
This comment has been minimized.
This comment has been minimized.
ok, wie willste das dann im Preview 2.0 machen wenn einer Mehl und der andere Hefe mag? Ich würde nur Produkt 1,2,3 machen und alles von außen eingeben. Andere Sache ist das ich zB hier 4 dm in unmittelbarer Nähe habe..... aber ich weiß schon das das exponentiell komplex wird so langsam ;) |
This comment has been minimized.
This comment has been minimized.
Zumindest letzteres wäre aktuell schon lösbar. Du müsstest vier widgets auf dem Homescreen platzieren und dann in den Einstellungen des Widgets die jeweilige Store ID als Parameter übergeben. Diese ID gewinnt dann gegenüber der im Skript konfigurierten: |
This comment has been minimized.
This comment has been minimized.
Ein Vorschlag, um die Land-Auswahl für Nutzer*innen einfacher zu machen: mehrere Parameter übergeben, einen davon als country, einen als storeID. Im Code sieht das dann so aus: Und so im Widget-Editor: Natürlich wäre auch hier die if-Abfrage möglich, ob überhaupt eine storeID übergeben wurde, habe ich der Einfachheit halber weg gelassen. Ich versuche gerade mit diesem Ansatz eine Lösung für die "Mehl-Auswahl" zu basteln, aber theoretisch ließe sich das ja erweitern, dass Nutzer*innen zwei Parameter übergeben, welche beiden Produkte sie gerne sehen würden, und dann anhand der Parameter die richtige dan-Liste im Script gewählt wird. Das wären dann vier Parameter Nur ein Gedankenspiel, wird natürlich immer komplexer dadurch, vor allem durch die unterschiedlichen Anfragen an den AT- und DE-Store ... Nachtrag: ein weiterer Vorteil, den ich ganz grundsätzlich sehe, wenn möglichst viele Anpassungen über Parameter laufen: Nutzer*innen müssen bei einem Script-Update nicht immer wieder Variablen im Code ändern, sondern lediglich das neue Script in den Widget-Settings auswählen. Nachtrag 2: ich habe meinen Fork jetzt dahingehend erweitert, dass eine Auswahl der Mehlsorte möglich ist. |
This comment has been minimized.
This comment has been minimized.
@CKone01 Das habe ich, wie oben beschrieben, bereits gemacht. Ich habe es allerdings nicht geschafft, mehrere Artikel gleichzeitig abzufragen und darzustellen. |
This comment has been minimized.
This comment has been minimized.
Sehr schönes Widget. Ich hatte heute die Möglichkeit implementiert Widgets per JSX-like Syntax in Scriptable zu erstellen (auf basis von htm). Mehr infos hier oder hier auf Twitter Da dieses Widget vom Layout recht Umfangreich ist habe ich es mal in JSX nachgebaut. Im Kern ist das der Code der zum erstellen des Widgets genutzt wird: async function createWidget() {
const logoImg = await getImage('dm-logo.png');
const icon = await getImage('toilet-paper.png');
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
}
return render`
<ListWidget padding=${[10,10,10,10]}>
<Spacer length=${4} />
<HStack>
<Spacer />
<Stack backgroundColor=${Color.white()} cornerRadius=${8}>
<Image image=${logoImg} size=${new Size(40, 40)} />
</Stack>
</HStack>
<Spacer />
<HStack>
<Spacer length=${2} />
<Image image=${icon} size=${new Size(40, 40)} />
<Spacer length=${13} />
<VStack>
<Text font=${Font.mediumRoundedSystemFont(13)}>KLOPAPIER</Text>
<Text
font=${Font.mediumRoundedSystemFont(22)}
color=${storeCapacity < 30 ? new Color("#E50000") : new Color("#00CD66")}
>${
storeCapacity.toString()
}
</Text>
</VStack>
</HStack>
<Spacer length=${4} />
<VStack>
<Text font=${Font.regularSystemFont(11)}>${storeInfo.address.street}</Text>
<Text font=${Font.regularSystemFont(11)}>${storeInfo.address.zip + ' ' + storeInfo.address.city}</Text>
${isOpen
? h`<Text font=${Font.mediumSystemFont(11)} color=${new Color("#00CD66")}>Geöffnet</Text>`
: h`<Text font=${Font.mediumSystemFont(11)} color=${new Color("#E50000")}>Geschlossen</Text>`
}
</VStack>
</ListWidget>
`;
} Da sich durch die Nutzung von JSX die Struktur etwas geändert hat (klare Trennung von Daten und Anzeige) musste ich leider auch die Hier der gesamter Quellcode
// Variables used by Scriptable.
// These must be at the very top of the file. Do not edit.
// icon-color: blue; icon-glyph: magic;
const country = 'de' // replace with 'at' for shops in Austria
const storeId = 251
const param = args.widgetParameter
if (param != null && param.length > 0) {
storeId = param
}
const storeInfo = await fetchStoreInformation()
const storeCapacity = await fetchAmountOfPaper()
const widget = 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');
const icon = await getImage('toilet-paper.png');
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
}
return render`
<ListWidget padding=${[10,10,10,10]}>
<Spacer length=${4} />
<HStack>
<Spacer />
<Stack backgroundColor=${Color.white()} cornerRadius=${8}>
<Image image=${logoImg} size=${new Size(40, 40)} />
</Stack>
</HStack>
<Spacer />
<HStack>
<Spacer length=${2} />
<Image image=${icon} size=${new Size(40, 40)} />
<Spacer length=${13} />
<VStack>
<Text font=${Font.mediumRoundedSystemFont(13)}>KLOPAPIER</Text>
<Text
font=${Font.mediumRoundedSystemFont(22)}
color=${storeCapacity < 30 ? new Color("#E50000") : new Color("#00CD66")}
>${
storeCapacity.toString()
}
</Text>
</VStack>
</HStack>
<Spacer length=${4} />
<VStack>
<Text font=${Font.regularSystemFont(11)}>${storeInfo.address.street}</Text>
<Text font=${Font.regularSystemFont(11)}>${storeInfo.address.zip + ' ' + storeInfo.address.city}</Text>
${isOpen
? h`<Text font=${Font.mediumSystemFont(11)} color=${new Color("#00CD66")}>Geöffnet</Text>`
: h`<Text font=${Font.mediumSystemFont(11)} color=${new Color("#E50000")}>Geschlossen</Text>`
}
</VStack>
</ListWidget>
`;
}
// 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 information of the configured store, e.g. opening hours, address etc.
async function fetchStoreInformation() {
const url = `https://store-data-service.services.dmtech.com/stores/item/${country.toLocaleLowerCase()}/${storeId}`
const widgetUrl = `https://www.dm.${country.toLocaleLowerCase()}/search?query=toilettenpapier&searchType=product`
let req = new Request(url)
let storeInfo = await req.loadJSON()
return { widgetUrl, ...storeInfo }
}
// 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()
}
// Library Code
function render(...args) {
const NativeElements = {
ListWidget(ast) {
return applyAttrs(new ListWidget(), ast.attrs);
},
Text(ast, parent) {
const { attrs, children } = ast;
parent = parent.addText(children.join(' '));
return applyAttrs(parent, attrs, {
color: 'textColor',
opacity: 'textOpacity',
center: 'centerAlignText',
left: 'leftAlignText',
right: 'rightAlignText',
});
},
Stack(ast, parent) {
parent = parent.addStack();
return applyAttrs(parent, ast.attrs, {
vertical: 'layoutVertically',
horizontal: 'layoutHorizontally',
});
},
VStack(ast, parent) {
parent = this.Stack(ast, parent);
parent.layoutVertically();
return parent;
},
HStack(ast, parent) {
parent = this.Stack(ast, parent);
parent.layoutHorizontally();
return parent;
},
Spacer(ast, parent) {
const { length, ...attrs } = ast.attrs;
parent = parent.addSpacer(ensureBase10Int(length));
return applyAttrs(parent, attrs);
},
Image(ast, parent) {
const { image, ...attrs } = ast.attrs;
parent = parent.addImage(image);
return applyAttrs(parent, attrs, {
size: 'imageSize',
opacity: 'imageOpacity',
fill: 'applyFillingContentMode',
fitting: 'applyFittingContentMode',
center: 'centerAlignImage',
left: 'leftAlignImage',
right: 'rightAlignImage',
});
},
Symbol(ast, parent) {
let { name, color, size, ...attrs } = ast.attrs;
const sfSymbol = SFSymbol.named(name);
// Look for a style in the props and call the apply*Weight() function
["bold", "black", "heavy", "light", "medium", "regular", "semibold", "thin", "ultralight"].forEach((attr) => {
if (attrs[attr]) {
sfSymbol['apply' + capitalize(attr) + 'Weight']();
}
});
if (size) {
size = ensureBase10Int(size);
if (typeof size !== "Size") {
size = new Size(size, size);
}
}
return this.Image({
...ast,
attrs: {
image: sfSymbol.image,
size: size || sfSymbol.image.size,
tintColor: color,
...attrs
}
},
parent
)
},
Date(ast, parent) {
const { date, ...attrs } = ast.attrs;
parent = parent.addDate(date);
return applyAttrs(parent, attrs, {
dateStyle: 'applyDateStyle',
offsetStyle: 'applyOffsetStyle',
timerStyle: 'applyTimerStyle',
center: 'centerAlignText',
left: 'leftAlignText',
right: 'rightAlignText',
});
},
};
// Call styles:
// render(h``)
// render(() => h``)
let ast = args[0]; // render(h``)
if (args.length >= 2 && Array.isArray(args[0])) { // render``
ast = h(...args);
} else if (args.length === 1) {
if (typeof args[0] === 'function') { // render(() => h``)
ast = args[0]();
}
}
if (ast[0].type !== 'ListWidget')
throw new Error(`A widget must be rendered inside <ListWidget>`);
return renderWidget(ast[0], null);
function capitalize(string) {
return string[0].toUpperCase() + string.slice(1);
}
function ensureBase10Int(stringOrNumber) {
if (typeof stringOrNumber === 'string') {
return parseInt(stringOrNumber, 10);
}
return stringOrNumber;
}
function call(element, name, values) {
if (Array.isArray(values)) {
element[name](...values);
} else {
element[name](values);
}
}
function applyAttrs(element, attributes, mapping) {
for (let name in attributes) {
let values = attributes[name];
name = (mapping && mapping[name]) || name;
let eType = typeof element[name];
if (eType === 'function') { // if it's a function call it
call(element, name, values);
} else if (eType === 'undefined') { // It's undefied
let setFn = 'set' + capitalize(name); // Lookup setter method
if (typeof element[setFn] === 'function') { // call it if it excists
call(element, setFn, values);
} else { // Fallback to default behaviour
element[name] = values;
}
} else { // Default to just set the property
element[name] = values;
}
}
return element;
}
function renderWidget(ast, parent) {
if (ast.type) {
if (NativeElements[ast.type] === undefined)
throw new Error(`Unkown Element <${ast.type} />`);
parent = NativeElements[ast.type](ast, parent) || parent;
if (ast && ast.children) {
for (let part of ast.children) {
renderWidget(part, parent);
}
}
} else if (Array.isArray(ast)) {
for (let part of ast) {
renderWidget(part, parent);
}
}
return parent;
}
}
// Using https://github.com/developit/htm
// License https://github.com/developit/htm/blob/master/LICENSE
function h(n){const t=(n,e,s,r)=>{let u;e[0]=0;for(let h=1;h<e.length;h++){const l=e[h++],p=e[h]?(e[0]|=l?1:2,s[e[h++]]):e[++h];3===l?r[0]=p:4===l?r[1]=Object.assign(r[1]||{},p):5===l?(r[1]=r[1]||{})[e[++h]]=p:6===l?r[1][e[++h]]+=p+"":l?(u=n.apply(p,t(n,p,s,["",null])),r.push(u),p[0]?e[0]|=2:(e[h-2]=0,e[h]=u)):r.push(p)}return r};return t(function(n,t,...e){return t=t||{},e=e||[],"function"==typeof n?(e=e.reverse(),n({...t,children:e})):{type:n,attrs:t,children:e}},function(n){let t,e,s=1,r="",u="",h=[0];const l=n=>{1===s&&(n||(r=r.replace(/^\s*\n\s*|\s*\n\s*$/g,"")))?h.push(0,n,r):3===s&&(n||r)?(h.push(3,n,r),s=2):2===s&&"..."===r&&n?h.push(4,n,0):2===s&&r&&!n?h.push(5,0,!0,r):s>=5&&((r||!n&&5===s)&&(h.push(s,0,r,e),s=6),n&&(h.push(s,n,0,e),s=6)),r=""};for(let p=0;p<n.length;p++){p&&(1===s&&l(),l(p));for(let c=0;c<n[p].length;c++)t=n[p][c],1===s?"<"===t?(l(),h=[h],s=3):r+=t:4===s?"--"===r&&">"===t?(s=1,r=""):r=t+r[0]:u?t===u?u="":r+=t:'"'===t||"'"===t?u=t:">"===t?(l(),s=1):s&&("="===t?(s=5,e=r,r=""):"/"===t&&(s<5||">"===n[p][c+1])?(l(),3===s&&(h=h[0]),s=h,(h=h[0]).push(2,0,s),s=0):" "===t||"\t"===t||"\n"===t||"\r"===t?(l(),s=2):r+=t),3===s&&"!--"===r&&(s=4,h=h[0])}return l(),h}(n),arguments,[])} Vielleicht macht das die Arbeit an diesem Widgets in Zukunft ja einfacher? |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Sorry, nimm die neueste Version, dann klappt es wieder. Im 1. Kommentar schreibe ich immer die Updates rein. |
This comment has been minimized.
This comment has been minimized.
Hatte ich eigentlich gemacht dann ging es erst nicht aber 5 min später oder so hat es sich gefangen ^^ vielen Dank |
This comment has been minimized.
This comment has been minimized.
@FWeinb |
This comment has been minimized.
This comment has been minimized.
Ja, so habe ich das schon in anderen Widgets umgesetzt. Ich muss es auf jeden Fall so implementieren, dass sämtliche möglichen Fehlerquellen validiert werden. Also mit Komma, ohne Komma, ohne alles, usw. Wenn irgendwas nicht gelesen werden kann, dann zurück auf defaults. |
This comment has been minimized.
This comment has been minimized.
das ja genial :-) gibts das auch für Rossmann ? |
This comment has been minimized.
This comment has been minimized.
No Problem - allein schon deine Idee war großartig und darüber freuen sich bestimmt viele Menschen - das alleine zählt! Achte nur drauf wenn du die andere if Condition einbauen möchtest, dass da noch ein bisschen was bei dem Index Zugriff gemacht werden muss - sonst fliegt das Sonntags wieder auf die Nase. |
This comment has been minimized.
This comment has been minimized.
Hat jemand schon den code für Sneak Preview 2.0?, möchte da keine 8 Widgets anlegen müssen, müsste dann nur noch 4x anlegen. |
This comment has been minimized.
This comment has been minimized.
Ich habe das zumindest vorerst mal als eigenes gist abgelegt. Kann getestet werden. → https://gist.github.com/marco79cgn/b13719df059d1e8d3277af8216a4d340 |
This comment has been minimized.
This comment has been minimized.
voll gute Idee! Idee für die nächsten Versionen: |
This comment has been minimized.
This comment has been minimized.
Sehr cool. Würde ich auch nehmen.Teilst du es? |
This comment has been minimized.
This comment has been minimized.
Rossmann bietet soweit ich weiß keine Bestandsabfrage an, sondern nur "Wird dieser Artikel in dem Store geführt ja/nein". Daher eher weniger sinnig dieses Widget dafür zu benutzen. |
This comment has been minimized.
This comment has been minimized.
Tolles Projekt. Danke dafür. |
This comment has been minimized.
This comment has been minimized.
Geiles Ding, ich liebe es! Bitte mehr davon. |
This comment has been minimized.
This comment has been minimized.
Leider wirft das Skript einen Error :/
|
This comment has been minimized.
This comment has been minimized.
Leider ja, achte darauf, alles bis zum Ende zu markieren. Der nächste Refresh könnte danach ein paar Minuten dauern, bis die Meldung verschwindet. |
This comment has been minimized.
This comment has been minimized.
Jup, jetzt klappts :D |
This comment has been minimized.
This comment has been minimized.
Wie bereits oben mehrfach erwähnt, ist dies bei den Märkten nicht möglich. |
This comment has been minimized.
This comment has been minimized.
Ich habe das Widget mal für Android nachgebaut (benötigt Automagic für Android). |
This comment has been minimized.
This comment has been minimized.
Ich hab spaßeshalber auch mal einen Kondom-Fork erstellt :D |
This comment has been minimized.
This comment has been minimized.
Sehr nice! Ich würde das gern anpassen, um es auf die Auslastung meines Gyms zu übertragen. |
This comment has been minimized.
This comment has been minimized.
So sieht der gesamte
In dem div verbirgt sich die prozentuale Auslastung. Ich bin eher in Python zu Hause und eher nicht in JS, daher keine spontane Lösung dafür. |
This comment has been minimized.
This comment has been minimized.
So kannst du die Auslastung aus dem HTML parsen:
|
This comment has been minimized.
This comment has been minimized.
Hello, könntest du noch mal genauer die Schritte beschreiben, wie du diesen Call extrahiert hast? Ich habe in Safari mit den Entwicklertools mir die Webseiteninformationen anzeigen lassen und nichts gefunden, weder im Netzwerk-Tab noch sonst iwo. |
This comment has been minimized.
This comment has been minimized.
Eine weitere Pressemitteilung: https://www.business-punk.com/2020/10/der-klopapiercounter-widget-zeigt-vorraetiges-klopapier-in-dm-maerkten-an/ allerdings noch mit dem Klopapier falsch rum. |
This comment has been minimized.
This comment has been minimized.
Kannst du da ein richtiges GitHub Repository draus machen? Dann kann man besser PR mit Codeverbesserungen etc einreichen. Auch releases etc kann man da besser auslesen etc |
This comment has been minimized.
This comment has been minimized.
Ich habe das ganze nun auf Android Portiert. Das ganze funktioniert mit KWGT. Ich werde die Tag eine Anleitung einstellen wie es konfiguriert wird. Eine Auflistung der Variablen findet ihr hier. Leider ist es nicht ganz so einfach wie bei iOS. |
This comment has been minimized.
This comment has been minimized.
Hehe, vielen Dank. :) Stand inzwischen schon bei RTL, Macwelt, Business Punk, Berliner Zeitung, iPhone-ticker, t3n, Caschys Blog, RND, Giga, Computer Bild, Chip, Curved, Notebooksbilliger, Vowe, Billiger-Telefonieren u.a. |
This comment has been minimized.
This comment has been minimized.
wie oft läuft das Script denn, also wie oft wird der Inhalt aktualisiert? |
This comment has been minimized.
This comment has been minimized.
Das bestimmt iOS und es hängt von diversen Faktoren ab (Interaktion, Batteriestatus, Energiesparmodus ja/nein, etc.). Grobe Faustregel: alle 5-8 Minuten wird es automatisch aktualisiert. |
This comment has been minimized.
This comment has been minimized.
danke für die Antwort - und noch mehr für das geniale Script! Mit was für einer Entwicklungsumgebung arbeitest du denn? Würde gerne ein bisschen damit rumspielen |
This comment has been minimized.
This comment has been minimized.
Danke für die Blumen. :) Das Grobe mach ich mit Visual Studio Code und speichere die Javascript Datei dann direkt im Scriptable Order in iCloud Drive, so dass sie direkt auf's iPhone/iPad gesynct wird. Den Feinschliff mach ich dann in der Scriptable App. Hier gibt es auch eine Art code completion, die speziell am Anfang sehr hilfreich ist. Eine Doku gibt es auch in der Scriptable App zum nachschlagen. |
This comment has been minimized.
This comment has been minimized.
Ich habe eine Android Version entwickelt! Hier ist mein Repository. Wer da auch reinschauen (und hoffentlich auch beitragen ;) ) will, ist herzlich willkommen! |
This comment has been minimized.
This comment has been minimized.
Dein Repository Link müsstest du nochmal bearbeiten und dein |
This comment has been minimized.
This comment has been minimized.
Upss! Danke! |
This comment has been minimized.
This comment has been minimized.
Mal eine frage an die Programmierer hier. Wie bekommt man das hin das er z.b das logo neben den Text macht und nicht untereinander? Man kann es zwar rechts zentrieren aber dennoch ist es in der neuen Zeile. Habt Ihr ggf. ein kleinen Tipp für mich oder ein kleines beispiel ums vielleicht besser zu verstehen? `
|
This comment has been minimized.
This comment has been minimized.
Das musst du mit Stacks machen.
Man kann stacks wiederum beliebig oft weiter stapeln. |
This comment has been minimized.
This comment has been minimized.
Vielen Dank für das tolle Widget |
This comment has been minimized.
This comment has been minimized.
Hey Marco, vielen dank für deinen Tipp. Nun hab ich es verstanden :) lg |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Ja klar, du kannst auch zwei Texte nebeneinander platzieren und dann unterschiedliche Schriftarten/-farben definieren. Sieht gut aus. |
This comment has been minimized.
This comment has been minimized.
Hey Marco, ich danke dir für deinen Tipp. Damit habe ich es so hinbekommen wie ich es mir vorgestellt hatte. Anbei nochmals einen Screenshot damit du die Veränderung zu vorher auch siehst. Ich denke das Widget kann sich Optisch sehen lassen :) Vielen Dank nochmal |
This comment has been minimized.
This comment has been minimized.
Habe es jetzt trotz fehlender Programmierkenntnisse geschafft, dass mir das Widget alle für mich relevanten Mehlsorten anzeigt. Und, durch deine Tipps mit den Stacks, dass das Logo unten rechts neben der Adresse angezeigt wird. Allerdings wird das Logo irgendwie nicht korrekt rechts ausgerichtet und nur durch einen Spacer nach rechts gedrückt. Deshalb wird das Widget bei überlangen Straßennamen breiter als es sein sollte. Ansonsten funktioniert alles super, nochmal vielen Dank für deine Arbeit! Link zum Fork: https://gist.github.com/veraverto/04767193de6f100b6223809154d21483 |
This comment has been minimized.
This comment has been minimized.
@marco79cgn, kannst du dein Script auch abändert für eine abfrage auf der OBI Seite? |
This comment has been minimized.
This comment has been minimized.
Theoretisch wäre das möglich, OBI hat auch Bestandsauskunft. Allerdings gibt's dort kein Klopapier. ;) |
This comment has been minimized.
This comment has been minimized.
Hehe, könntest du vielleicht das dm Script auf obi abändern? |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Hat den nicht jemand mal die Zeit und die Lust ein Widget für Unwetterwarnungen vom DWD zu machen? Das wäre mal nützlich und hilfreich. https://www.dwd.de/DE/wetter/warnungen_landkreise/warnWetter_node.html Die Daten können per JSON abgerufen werden. Sollte doch möglich sein. |
This comment has been minimized.
This comment has been minimized.
Die Frage ist (wie grundsätzlich bei allen Widgets), ob man das wirklich 24/7 auf seinem Homescreen haben will und dafür Platz opfert. Gerade eine Unwetter-Warnung ist meiner Ansicht nach eher geeignet für Push-Mitteilungen. Denn die meiste Zeit wird das Widget sonst einfach nichts anzeigen. |
This comment has been minimized.
This comment has been minimized.
Aber in Widgets lassen sich doch auch Push Mitteilungen einbauen. Zumindest nutze ich eins was es kann. Auch zb nutzbar für Klopapier Knappheit. |
This comment has been minimized.
This comment has been minimized.
Marco hat diesbezüglich nicht ganz unrecht, und ein Widget nur für ne Push-Benachrichtigung, ich weiß nicht. Da geht der sinn eines Widgets verloren. Aber es hält dich sicherlich keiner auf es umzusetzen. :P |
This comment has been minimized.
This comment has been minimized.
Tolles Skript! Das Issue ist mir auch aufgefallen, habe nun die Produkt IDs für "Toilettenpapier Roll" gefunden: hierfür die IDs an dieser Stelle ändern: |
This comment has been minimized.
This comment has been minimized.
Das Issue, was du zitierst hast, war kein Issue, sondern da wurden lediglich Äpfel (Zielseite, wenn man auf das Widget klickt) mit Birnen (tatsächliche dans im Skript) verglichen. Was genau sind jetzt "Toilettenpapier Roll"? Ich hatte vorher sämtliche dans händisch rausgesucht, die auch wirklich Toilettenpapier sind (kein Feuchtpapier) und nur diese berücksichtigt für den Counter. Nur beim Aufruf der Shop-Seite klappt das halt nicht. |
This comment has been minimized.
This comment has been minimized.
Stimmt, es war kein Issue. "Toilettenpapier Roll" hatte die Idee, dass tatsächlich nur gerolltes Toilettenpapier und keine Feuchttücher und anderweitige Produkte berücksichtigt werden, die in der Suche nach "Toilettenpapier" auftauchen. Habe eben aber nachgesehen und es gibt ja tatsächlich Produkte, die berücksichtigt werden sollten, ohne dass sie "Roll..." im Namen haben. |
This comment has been minimized.
This comment has been minimized.
Hi, kann mir einer von euch sagen, ob und wie das auch für eine Abfrage bei Ikea möglich wäre? |
This comment has been minimized.
Intro
Das dm Klopapier Widget zeigt die Vorräte an Toilettenpapier in deiner nächsten dm Drogerie. Die einzig zuverlässige #Klopapiergarantie.
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.
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.
Installation
Danke
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.
Updates
24.10.2020, 00:24
WICHTIG: Bitte aktualisiert auf die neueste Version, damit das Skript auch an Samstagen und Sonntagen funktioniert. Sorry!
23.10.2020, 17:49
→ bitte oben das Land (country) auf 'at' setzen für Österreich
23.10.2020, 00:05
22.10.2020, 18:35
22.10.2020, 18:53