-
Notifications
You must be signed in to change notification settings - Fork 6
/
Copy pathTestFlight Watcher.scriptable
12 lines (11 loc) · 7.23 KB
/
TestFlight Watcher.scriptable
1
2
3
4
5
6
7
8
9
10
11
12
{
"always_run_in_app" : false,
"icon" : {
"color" : "blue",
"glyph" : "plane"
},
"name" : "TestFlight Watcher",
"script" : "\/*\nTestFlight Watcher by FifiTheBulldog\n\nA widget to watch for openings in TestFlight betas and notify you when a spot is available.\n*\/\n\n\/*\nThis script is intended to be used as a widget on your home screen. You may also set up automations in Shortcuts to run this script, to check for TestFlight openings at times other than the ones dictated by Scriptable's normal widget refresh interval.\n*\/\n\n\/*\nSpecify the default TestFlight ID or URL (either works) and the app name in the \"prefs\" object below (begins on line 49).\nThese will be overridden if the TestFlight link and app name are supplied as a widget parameter.\n\nYou can also use a dictionary of the same form as the shortcut parameter when running this script from a shortcut.\n*\/\n\n\/*\nTo specify the app name and TestFlight link as a widget parameter, use the following syntax in the widget configurator's \"Parameter\" field:\n\n TESTFLIGHT_ID|APP_NAME\n\nwhere the TestFlight ID\/link and app name are separated by a vertical bar (|).\n\nFor example:\n\n uN1vTqxk|Scriptable\n\nspecifies the Scriptable beta.\n*\/\n\n\/*\nAs mentioned above, you can also pass in arguments from a shortcut, using the Run Script action. There are three ways to do this:\n\n- Use a dictionary with the same format as prefs as the shortcut parameter (recommended)\n- Set the app name to be the first item in the Texts field, and set the TestFlight ID or URL as the first item in the URLs field\n- Set the TestFlight ID or URL as the first item in the Texts field, and set the app name as the second item in the Texts field (similar to the order used for widget parameters)\n*\/\n\n\/*\nPreferences - you can change these values:\n\n- testFlight: TestFlight ID or URL\n- appName: name of TestFlight app\n- notify: whether to send a notification when there is an open beta spot. Defaults to true unless explicitly set to false.\n- sound: whether the notification for an open beta spot should have a sound. Defaults to false.\n*\/\n\nconst prefs = {\n testFlight: \"uN1vTqxk\",\n appName: \"Scriptable\",\n notify: true,\n sound: false\n};\n\n\/*\nHappy TestFlight sniping!\n*\/\n\n\/* SCRIPT BEGINS HERE *\/\n\n\"use strict\";\n\n\/\/ Handle \"Copy Link\" notification action\n\/\/ If you run this script with its URL scheme, and add a parameter called \"copyTFLink\" with a full TestFlight URL as its value, that will have the same effect as tapping the \"Copy Link\" action in a notification for when a beta spot is open.\nif (args.queryParameters.copyTFLink) {\n Pasteboard.copyString(args.queryParameters.copyTFLink);\n return; \/\/ Exit script immediately\n}\n\n\/\/ In case prefs gets deleted\nif (typeof prefs !== \"object\" || prefs === null) globalThis.prefs = {};\n\n\/\/ Widget parameter overrides preferences in script\nif (args.widgetParameter) {\n const paramElements = args.widgetParameter.trim().split(\"|\");\n prefs.testFlight = paramElements[0].trim();\n if (paramElements.length > 1) {\n prefs.appName = paramElements.slice(1).join(\"|\").trim();\n }\n}\n\nfunction paramsToPrefs(params) {\n for (const pref of [\"testFlight\", \"appName\", \"notify\", \"sound\"]) {\n if (pref in params) prefs[pref] = params[pref];\n }\n}\n\n\/\/ If run from a notification, attempt to use the userInfo property for prefs\nif (args.notification) {\n paramsToPrefs(args.notification.userInfo);\n}\n\n\/\/ If run from a URL scheme, attempt to use the parameters for prefs\nif (args.queryParameters) {\n paramsToPrefs(args.queryParameters);\n}\n\n\/\/ If run from a shortcut, attempt to use args.plainTexts and args.urls as input\nif (args.plainTexts.length !== 0) {\n if (args.plainTexts.length > 1) {\n let i = 0;\n while (i < args.plainTexts.length) {\n if (args.plainTexts[i]) {\n switch (i) {\n case 0:\n prefs.testFlight = args.plainTexts[i];\n break;\n case 1:\n prefs.appName = args.plainTexts[i];\n break;\n default:\n continue;\n }\n i++;\n }\n }\n } else {\n if (args.urls.length !== 0) {\n prefs.testFlight = args.urls[0];\n prefs.appName = args.plainTexts[0];\n } else {\n prefs.testFlight = args.plainTexts[0];\n }\n }\n}\n\n\/\/ If run from a shortcut, attempt to use the shortcut parameter (overrides args.plainTexts and args.urls)\nif (args.shortcutParameter) {\n paramsToPrefs(args.shortcutParameter);\n}\n\n\/\/ Default prefs\n\/\/ Default to Scriptable beta if no beta specified in widget or prefs\n\/\/ sound defaults to false unless explicitly set to true (or a truthy value)\nif (!prefs.testFlight) prefs.testFlight = \"uN1vTqxk\";\nif (!prefs.appName) {\n prefs.appName = (prefs.testFlight === \"uN1vTqxk\") ? \"Scriptable\" : \"TestFlight\";\n}\nif (!(\"notify\" in prefs)) prefs.notify = true;\n\n\/\/ Parse TestFlight link for ID\nconst testFlightID = prefs.testFlight\n \/\/ Remove any query\n .replace(\/\\?.*\/, \"\")\n \/\/ Remove any trailing slash\n .replace(\/\\\/$\/, \"\")\n \/\/ Split URL by slashes\n .split(\"\/\")\n \/\/ Get last item (ID) in split URL\n .slice(-1)[0];\n\nconst testFlightURL = \"https:\/\/testflight.apple.com\/join\/\" + testFlightID;\n\n\/\/ Download and scrape TestFlight page\nconst r = new Request(testFlightURL);\nr.headers = {\n \"Accept-Language\": \"en-us\"\n};\nconst wv = new WebView();\nawait wv.loadRequest(r);\nconst betaInfo = await wv.evaluateJavaScript(`\n \"use strict\";\n const statusText = document.getElementsByClassName(\"beta-status\")[0]\n .getElementsByTagName(\"span\")[0]\n .innerText;\n let status;\n if (statusText === \"This beta is full.\") status = \"full\";\n if (statusText.startsWith(\"This beta isn't accepting\")) status = \"closed\";\n if (!status) status = \"open\";\n let iconURL;\n if (status !== \"closed\") {\n iconURL = document.getElementsByClassName(\"app-icon\")[0]\n .style\n .backgroundImage;\n }\n completion({ status, iconURL });\n`, true);\n\nconst widget = new ListWidget();\n\nif (betaInfo.iconURL) {\n const imageReq = new Request(betaInfo.iconURL.match(\/url\\(\"(.*)\"\\)\/)[1]);\n const icon = widget.addImage(await imageReq.loadImage());\n icon.centerAlignImage();\n icon.containerRelativeShape = true;\n\n widget.addSpacer(null);\n}\n\nconst messageText = prefs.appName + \" beta is \" + betaInfo.status;\n\nwidget.addText(messageText).centerAlignText();\n\nif (betaInfo.status === \"open\") {\n widget.backgroundColor = Color.green();\n \n if (prefs.notify) {\n const copyURL = new CallbackURL(URLScheme.forRunningScript());\n copyURL.addParameter(\"copyTFLink\", testFlightURL);\n \n const n = new Notification();\n n.title = messageText;\n n.body = `Join the ${prefs.appName} beta now`;\n n.openURL = testFlightURL;\n if (prefs.sound) n.sound = \"default\";\n n.addAction(\"Copy Link\", copyURL.getURL());\n n.schedule();\n }\n}\n\n\/\/ Preview widget unless environment is unsupported\nif (!(config.runsInWidget || config.runsInNotification)) await widget.presentSmall();\n\nScript.setWidget(widget);\nif (config.runsWithSiri) Script.setShortcutOutput(messageText);\nScript.complete();",
"share_sheet_inputs" : [
]
}