Skip to content

Instantly share code, notes, and snippets.

@marco79cgn
Last active November 8, 2023 07:28
Show Gist options
  • Star 14 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save marco79cgn/40ce08a1735dede2ab35acf375b6a4df to your computer and use it in GitHub Desktop.
Save marco79cgn/40ce08a1735dede2ab35acf375b6a4df to your computer and use it in GitHub Desktop.
Custom iOS widget that shows both the store and online availability of a given product (for Scriptable.app)
// Variables used by Scriptable.
// These must be at the very top of the file. Do not edit.
// icon-color: blue; icon-glyph: magic;
// default zip and partNo - will be overwritten by your widget parameters
let zip = '50670'
let partNo = "MU7A3ZD/A"
// insert your ntfy url
const notifyUrl = "https://ntfy.sh/******"
// force push notification - set to true in order to test that your setup is working correctly
const forcePushNotification = false
let storeName
let city
let params = getParams(args.widgetParameter)
const shopAvailability = await getShopAvailability()
const onlineAvailability = await getOnlineAvailability()
loadCachedAvailability()
let widget = new ListWidget()
widget.setPadding(0, 8, 6, 8)
let appleText = widget.addText("")
appleText.centerAlignText()
appleText.font = Font.boldMonospacedSystemFont(24)
widget.addSpacer(2)
let productText = widget.addText(shopAvailability.product)
productText.font = Font.boldRoundedSystemFont(13)
// productText.textColor = Color.orange()
productText.textOpacity = 1
productText.lineLimit = 4
productText.minimumScaleFactor = 0.8
productText.centerAlignText()
widget.addSpacer(4)
let onlineText = widget.addText("Online")
onlineText.font = Font.semiboldRoundedSystemFont(11)
onlineText.textOpacity = 0.5
onlineText.centerAlignText()
let onlineAvailabilityText = widget.addText(onlineAvailability)
onlineAvailabilityText.font = Font.semiboldRoundedSystemFont(11)
onlineAvailabilityText.textOpacity = 1
onlineAvailabilityText.centerAlignText()
widget.addSpacer(3)
let storeText
if(shopAvailability.storeName !== shopAvailability.city) {
storeText = widget.addText(shopAvailability.storeName + ' ' + shopAvailability.city)
} else {
storeText = widget.addText(shopAvailability.city)
}
storeText.font = Font.semiboldRoundedSystemFont(11)
storeText.textOpacity = 0.5
storeText.lineLimit = 2
storeText.minimumScaleFactor = 0.8
storeText.centerAlignText()
let availabilityText = widget.addText(shopAvailability.message)
availabilityText.font = Font.semiboldRoundedSystemFont(11)
if (shopAvailability.message.toLowerCase().indexOf('nicht') >=0) {
availabilityText.textColor = new Color("#DF0000")
await saveStatus("unavailable")
} else {
availabilityText.textColor = Color.green()
// check if unavailable before
const previousStatus = await loadCachedAvailability()
if (previousStatus == "unavailable") {
await sendNotification()
}
await saveStatus("available")
}
if(forcePushNotification) {
await sendNotification()
}
availabilityText.textOpacity = 1
availabilityText.minimumScaleFactor = 0.8
availabilityText.centerAlignText()
widget.url = "https://store.apple.com/de/xc/product/" + params.partNo
if(config.runsInApp) {
widget.presentSmall()
}
Script.setWidget(widget)
Script.complete()
// fetches the local shop availability
async function getShopAvailability() {
let availabilityMessage
let productName
const url = "https://www.apple.com/de/shop/retail/pickup-message?pl=true&parts.0="
+ params.partNo + "&location=" + params.zip
let req = new Request(url)
try {
let result = await req.loadJSON()
const store = result.body.stores[0]
// console.log("result: " + JSON.stringify(result))
const item = result.body.stores[0].partsAvailability[params.partNo]
availabilityMessage = item.pickupSearchQuote
productName = item.messageTypes.regular.storePickupProductTitle
storeName = store.storeName
city = store.city
} catch(exception){
console.log("Exception Occured.")
availabilityMessage = "N/A"
productName = "Keine Internetverbindung"
storeName = "Oder ungültige"
city = "PartNo"
}
return { "message" : availabilityMessage, "product" : productName , "storeName" : storeName, "city" : city }
}
// fetches the online store availability
async function getOnlineAvailability() {
let deliveryDate
const url = "https://www.apple.com/de/shop/delivery-message?mt=regular&parts.0=" + params.partNo
let req = new Request(url)
try {
let result = await req.loadJSON()
// console.log(result)
deliveryDate = result.body.content.deliveryMessage[params.partNo].regular.deliveryOptions[0].date
} catch(exception){
console.log("Exception Occured. " + exception)
deliveryDate = "N/A"
}
return deliveryDate
}
function getParams(widgetParams) {
if(widgetParams) {
let split = widgetParams.split(';')
partNo = split[0]
if(split.length > 1) {
zip = split[1]
}
}
return { "partNo" : partNo, "zip" : zip }
}
async function sendNotification() {
let req = new Request(notifyUrl)
req.method = "POST"
if(forcePushNotification) {
req.headers = {
"Title": "*TEST MODE* Apple Store",
"Click": "https://store.apple.com/de/xc/product/" + params.partNo,
"Tags": "apple"
}
} else {
req.headers = {
"Title": "Apple Store",
"Click": "https://store.apple.com/de/xc/product/" + params.partNo,
"Tags": "apple"
}
}
req.body = shopAvailability.product +" ist jetzt verfügbar im Apple Store " + storeName + " in " + city + "! (Tap to order)"
req.loadJSON()
}
async function loadCachedAvailability() {
let fm = FileManager.iCloud()
let dir = fm.documentsDirectory()
let partNoEscaped = params.partNo.replace("/", "-")
let path = fm.joinPath(dir, partNoEscaped + ".txt")
let lastStatus = Data.fromFile(path)
if (lastStatus != null) {
return lastStatus.toRawString()
} else {
return "first-call"
}
}
async function saveStatus(status) {
let fm = FileManager.iCloud()
let dir = fm.documentsDirectory()
let partNoEscaped = params.partNo.replace("/", "-")
let path = fm.joinPath(dir, partNoEscaped + ".txt")
fm.writeString(path, status)
}
@marco79cgn
Copy link
Author

marco79cgn commented Nov 11, 2020

Intro

iOS custom Widget for the Scriptable app. It shows both the store and online availability of a given Apple product. A tap on the widget opens the Apple Store app so you can place your order quickly. If the product is available again in your next Apple Store, you'll receive a push notificaction via ntfy.sh.

Requirements

Setup

  • install the notify app and create a new topic for your notifications. choose a unique topic name, something like "my-secret-apple-alerts" and subscribe to it
  • create a new script in the scriptable app, copy/paste the script above and save it.
  • insert your Notify URL (topic) in the script (line 8)
  • configure the widget on your homescreen with the following parameter scheme (separated by semicolon):
    MJ123D/A;50670;de

MJ123D/A → the Apple product identifier, also known as "part number" or "order number"
50670 → your zip code (for the nearest Apple Store)
de → your country code (de = Germany, uk = United Kingdom, etc.)

The easiest way to obtain the unique product identifier is by selecting your item in the official Apple Store iOS App and then pressing the "share" button. You'll get a url like this one: https://store.apple.com/de/xc/product/MJ123D/A

Optional: you can test if your notifications are working by setting the attribute forcePushNotification to true (line 10).

Thanks

A big Thank you to @simonbs for making great apps like Scriptable, DataJar or Jayson.

@monza258
Copy link

Danke für das wundervolle Script. Gibt es auch eine Möglichkeit bereits bestellte Artikel zu sehen. Also ein Script das
einem den Status einer Bestellung anzeigt?

@monza258
Copy link

So zb wird es in der App von Apple angezeigt.

3C792514-35BC-4F73-9E6A-D7156AE7B5C5

@marco79cgn
Copy link
Author

@monza258
Da war ich zu langsam, das baut inzwischen ein anderer. ;)
Schau mal hier.

@moxmlb
Copy link

moxmlb commented Nov 12, 2020

Ich habe mal einen Preis Beobachter für iTunes Filme gebaut. Vielleicht interessant für den einen oder anderen 😉 https://gist.github.com/moxmlb/a66f1592fb145ff5517d6028bc697880

@IronBreed
Copy link

Hallo!

Danke erstmal für die vielen tollen Scripts. Kann ich das eigentlich auch umarbeiten, um mir Lagerbestände auf anderen Websites anzeigen zu lassen?

@asyba
Copy link

asyba commented Oct 19, 2021

@marco79cgn when it does the refresh to check the stock? every time that Is scroll the page where the widget is on?

@marco79cgn
Copy link
Author

@asyba Since I'm not using any cache, only iOS decides when the content of the widget is refreshed. It's roughly every 5-10 minutes.

@asyba
Copy link

asyba commented Oct 19, 2021

@marco79cgn is possible to add a small button to refresh with scriptable? to interact with the widget? not sure if possible with iOS widgets?

@marco79cgn
Copy link
Author

Unfortunately that's not possible (iOS restrictions).

@osxdoc
Copy link

osxdoc commented Mar 22, 2022

@asyba
Copy link

asyba commented Mar 22, 2022 via email

@Apple-MacMarcus
Copy link

Hey there, thanks for the good work. Could you also limit the item view to one specific Apple Store, eg if I don't want to see nearby stores but only one specific? Thanks!

@jocheno
Copy link

jocheno commented Jul 29, 2022

Hi @marco79cgn, thank you for your wonderful script. Unfortunately it does not work any longer:

2022-07-29 08:38:37: Error on line 17:33: Expected value of type string but got value of type undefined.

Any recent changes to the Apple interface?

@jocheno
Copy link

jocheno commented Aug 10, 2022

Found it. Had to change line 82 to:
productName = item.messageTypes.regular.storePickupProductTitle

@marco79cgn
Copy link
Author

Update 17.09.2023, 21:17 Uhr

  • fixed script
  • added notifications via ntfy app

→ please follow the updated instructions in the first comment!

@r32er
Copy link

r32er commented Sep 22, 2023

bei mir scheitert es bereits beim topic für subscribe in notify. der button ist grau..

@marco79cgn
Copy link
Author

@r32er
Einfach in der App oben rechts auf das „+“ tippen und einen beliebigen Namen eingeben, danach auf Subscribe.
IMG_2490

@r32er
Copy link

r32er commented Sep 22, 2023

@r32er Einfach in der App oben rechts auf das „+“ tippen und einen beliebigen Namen eingeben, danach auf Subscribe.)

und genau das subscribe bleibt grau geht nicht. ah lel lag am leerzeichen sorry.

und die "ntfy.sh/iphone15...." muss dann in line 8 im script rein?

@NickCrack
Copy link

Hi Marco,

kannst du mir sagen warum ich diesen Schritt benötige?

configure the widget on your homescreen with the following parameter scheme (separated by semicolon):
MJ123D/A;50670;de

Ich habe Scriptable unter meinen Widgets hinzugefügt.

Unter Widget bearbeiten eingestellt:
IMG_2757

Weshalb sollte ich unter Parameter nun MJ123D/A;50670;de eingeben?

In deinem Script sind diese Werte doch bereits festgelegt!?

Da ich 3 Apple Stores in der Nähe habe, habe ich dein Script zweimal kopiert und die PLZ geändert.
Würde es auch einfacher gehen?

Besten Dank

@marco79cgn
Copy link
Author

Hi Nick,
du hast dir deine Frage bereits selbst beantwortet. ;)
Wenn du die Parameter in den Widget Einstellungen vornimmst, musst du das Skript selbst überhaupt nicht anfassen bzw. editieren. Zudem musst du es nur ein einziges Mal in Scriptable hinterlegen und kannst dennoch mehrere Widgets für unterschiedliche Produkte oder Postleitzahlen auf deinem Homescreen anlegen.

Die Kodierung im Skript selbst ist nur ein Fallback, falls man keine Parameter im Widget selbst gesetzt hat. Letztere haben aber Priorität, sofern vorhanden.

@NickCrack
Copy link

Hi Nick, du hast dir deine Frage bereits selbst beantwortet. ;) Wenn du die Parameter in den Widget Einstellungen vornimmst, musst du das Skript selbst überhaupt nicht anfassen bzw. editieren. Zudem musst du es nur ein einziges Mal in Scriptable hinterlegen und kannst dennoch mehrere Widgets für unterschiedliche Produkte oder Postleitzahlen auf deinem Homescreen anlegen.

Die Kodierung im Skript selbst ist nur ein Fallback, falls man keine Parameter im Widget selbst gesetzt hat. Letztere haben aber Priorität, sofern vorhanden.

Vielen Dank und macht absolut Sinn 👍☺️

Da bin ich doch mal gespannt ob ich so mein iPhone bekomme.

Die Frage bezüglich Aktualisierung hattest du weiter oben ja bereits beantwortet

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