More example scripts

The following is an example of Shortcuts (the upcoming successor to Workflow) calling a Scriptable script using the Scriptable URL scheme. Rather than specifically using args, I think this is at least half of what is being sought here in terms of being able to pass data fom one app to the other.

Expand for screenshots of example Shortcuts shortcut and the result in a Scriptable script

Shortcuts shortcut;

Scriptable script:

My only issue was it didn’t seem like Scriptable supported being called through an x-callback-url (though there was/is documentation on x-callback-urls from Scriptable). I couldn’t see anyway to set the x-success result and pass control back to the calling app. Have I missed this in the documentation or is this something that there are plans to introduce?

2 Likes

You can indeed open Scriptable using a x-callback-url. There’s no built in way of handling this yet as it’s a little difficult to define a behaviour that covers all possible cases. Instead scripts can easily call x-success, x-error or x-cancel for now.

When you run the script from Shortcuts, you’ll get the following query parameters:

  • x-success: Your script should call this on success to hand back the control to Shortcuts (or any other third-party app)
  • x-error: Your script should call this when an error occurs to hand back the control to Shortcuts (or any other third-party app)
  • x-cancel: Your script should call this when further execution of the script is cancelled to hand back the control to Shortcuts (or any other third-party app)

x-success, x-error and x-error are themselves URL schemes. Invoking one of them looks like this:

let queryParams = URLScheme.allParameters()
let successURL = queryParams["x-success"]
Safari.open(successURL)
3 Likes

Backup Shortcuts
Inspired by Rosemarys shortcut to backup shortcuts, I’ve created a shortcut/script that backs up shortcuts to a Git repository using Working Copy.

Prerequisites:

  1. Create a directory in Working Copy you want to backup your scripts into.
  2. Set the key variable to your Working Copys URL key. You’ll find it in Working Copy -> Settings -> App Integrations.

The following operations are performed:

  1. Shortcuts: Removes all files in the Shortcuts/Export directory in iCloud.
  2. Shortcuts: Copies all shortcuts into the Shortcuts/Export directory in iCloud.
  3. Shortcuts: Opens Scriptable to run a script.
  4. Scriptable: Prompts you to select the Shortcuts/Export directory in order to grant Scriptable access to its content.
  5. Scriptable: Prompts you to select the repository in Working Copy to backup scripts to in order to grant Scriptable access to its content.
  6. Scriptable: Copies files from the Shortcuts/Export directory into the repository.
  7. Scriptable: Opens Working Copy to commit and push the changes.
  8. Working Copy: Commits and pushes changes.
  9. Working Copy: Opens Scriptable to complete the flow.
  10. Scriptable: Opens Shortcuts to complete the flow.

You’ll need this Shortcut which you run to start the backup:
https://www.icloud.com/shortcuts/c2f36e770ac7465caebe33bdd583d873

Script:

// Find your key in Working Copy and insert it below. This is required by Working Copy in order to perform operations.
let key = ""
await promptDirSelection("Please navigate to the Shortcuts folder in iCloud Drive and open the Export folder.")
let fm = FileManager.iCloud()
let shortcutsDirs = await DocumentPicker.open(["public.folder"])
let shortcutsDir = shortcutsDirs[0]
let filePaths = allFiles(shortcutsDir)
await promptDirSelection("Please use the Working Copy file provider to open the repository you want to back up to.")
let repoDirs = await DocumentPicker.open(["public.folder"])
let repoDir = repoDirs[0]
let repoName = fm.fileName(repoDir)
removeFiles(repoDir)
for (filePath of filePaths) {
  copyFile(filePath, repoDir)
}
await pushChanges(repoName, key)
let params = URLScheme.allParameters()
let url = params["x-success"]
Safari.open(url)

async function pushChanges(repo, key) {
  let baseURL = "working-copy://x-callback-url/chain/"
  let msg = "Backup from Scriptable"
  let cbu = new CallbackURL(baseURL)
  cbu.addParameter("key", key)
  cbu.addParameter("repo", repo)
  cbu.addParameter("command", "commit")
  cbu.addParameter("message", msg)
  cbu.addParameter("limit", "999")
  cbu.addParameter("command", "push")
  await cbu.open()
}

function copyFile(srcFilePath, dstDir) {
  let fm = FileManager.iCloud()
  let filename = fm.fileName(filePath, true)
  let dstFilePath = fm.joinPath(dstDir, filename)
  fm.copy(srcFilePath, dstFilePath)
}

function removeFiles(dstDir) {
  let fm = FileManager.iCloud()
  let filePaths = allFiles(dstDir)
  for (filePath of filePaths) {
    fm.remove(filePath)
  }
}

function allFiles(dir) {
  let fm = FileManager.iCloud()
  let files = fm.listContents(dir)
  let filePaths = files.map(file => {
    return fm.joinPath(dir, file)
  })
  return filePaths.filter(isShortcut)
}

function isShortcut(filePath) {
  let fm = FileManager.local()
  let ext = fm.fileExtension(filePath)
  return ext == "shortcut"
}

async function promptDirSelection(msg) {
  let alert = new Alert()
  alert.title = "Select folder"
  alert.message = msg
  alert.addAction("OK")
  await alert.presentAlert()
}
3 Likes

I tried to use the Overcast to Bear example script and receive the following error. Can you please point me in the right direction? Thanks.
Error: TypeError: html.match is not a function. (In ‘html.match(titleRegExp)’,‘html.match’ is undefined)

Thanks for reporting the issue with the script.

The script was using Request.load() which has its behavior changes during the beta. I’ve changed it to Request.loadString() and the script should work now.

Hello,
I am trying to modify the script below:

How can I email attendees in the same script? I can reach attendees in Shortcuts, but not in Scriptable, it seems?

Thank you for that info and updating the script. It works great now.

Attendees are not yet exposed in Scriptable but that would be a great addition. I’ve added it to the backlog.

For now, maybe you could hand off control to Shortcuts and let it fetch the attendees.

1 Like

Thank you, I will check how to do the call Scriptable => Shortcuts ( => Scriptable)

I have looked further into adding attendees. The attendees of an event will be exposed in the next updated. Unfortunately, iOS doesn’t allow third party apps to add or modify the attendees of an event as described in Apple docs.

1 Like

Great, thank you for having researched!

hi there,

just starting with scriptable. very cool so far.

one suggestion for your commit scripts — both the first standalone one, and this newer one that works with shortcuts:

i’m also a very new user of working copy. combine all the newness here, and there were some initial runs of the standalone script that failed within working copy.

this was enough to delete all of the scripts i had made from my icloud directory, with no way to recover, as mentioned would be the case in the docs for FileManager.remove(filepath)

luckily it’s early enough that there wasn’t anything crucial in there yet, but as i’m sure is the case with most coders, the first few things i made took great time and effort to learn the code quickly, and i’d hoped would serve as reference for the more substantial things i plan to make.

if i were you, i’d rewrite the call to remove the files until all else is successful, so that this doesn’t happen to others. maybe a temp copy of everything first?

i think i’d also make comments about what needs to be done in working copy more specific, and to move the instructional stuff you do list in the posts above directly into comments in the scripts, so that they are there as you examine them in the app. i think the script failed once because i hadn’t yet created a remote for my backup directory. i don’t remember if you mentioned this in the latter script, but i don’t think you did in the standalone one. finally, i think in the first script, it would also help to explicitly name the working copy key more clearly, as you did in the post surrounding the latter script — “Working Copys URL key.” dumb of me, but i first tried the ssh pub key listed not far from the url key in the working copy config.

hope this all makes sense. let me know if not.

otherwise, thank you for the awesome app and your energy here on the forum!

Is it in some TestFlight version?
I don’t receive any prompt to approve Health access.

Unfortunately Apple did not allow Scriptable onto the App Store with the health integration so it’s no longer in the app.

3 Likes

Thank you for the prompt response.
Any plans to add it in the future?
Or probably get the data through another app with a callback? IFTTT has access to the Health but write-only (if I remember correctly).

I hope to bring it back in a future update. I just have to figure out an approach that will make Apples review team happy.

4 Likes

I am Trying to run the FileManager Script you posted above.
I copied the exact code, but I receive an error:
„Expected value of type Data but got value of type string“.

Can you tell me how to fix that?

That’s quite an old example and some of the functions changed a little in the mean time. In particular, check out the FileManager documentation in the app.

Try this.

let fm = FileManager.iCloud()
let dir = fm.documentsDirectory()
let path = fm.joinPath(dir, "myfile.txt")
fm.writeString(path, "Hello world")
let text = fm.readString(path)
await QuickLook.present(text)

Hope that helps.

I’ve few m3u8 streaming urls that i would like to play using your wwdc example which currently YT.Player API.
Can you please share an example?

1 Like

I found one and tested as working.

// Variables used by Scriptable.
// These must be at the very top of the file. Do not edit.
// icon-color: deep-brown; icon-glyph: magic-wand;
let html = `
<style>
body {
  margin: 0;
  padding 0;
  background: black;
}
.vid {
  width: 100%;
  height: 100%;
}
</style>
<div id="player"></div>
<script src="https://cdn.jsdelivr.net/npm/hls.js@latest"></script>
<video id="video"></video>
<script>
  var video = document.getElementById('video');
  if(Hls.isSupported()) {
    var hls = new Hls();
    hls.loadSource('https://video-dev.github.io/streams/x36xhzz/x36xhzz.m3u8');
    hls.attachMedia(video);
    hls.on(Hls.Events.MANIFEST_PARSED,function() {
      video.play();
  });
 }
 else if (video.canPlayType('application/vnd.apple.mpegurl')) {
    video.src = 'https://video-dev.github.io/streams/x36xhzz/x36xhzz.m3u8';
    video.addEventListener('loadedmetadata',function() {
      video.play();
    });
  }
</script>
`
WebView.loadHTML(html, null, new Size(0, 202))
1 Like