For a while, you've been able to script Mac OS using JavaScript using JavaScript for Open Scripting Architecture (JSOSA). This is a huge improvement on AppleScript. Why? JavaScript isn't a great language, but it broadly follows the conventions that programming languages of the C/ALGOL family have followed for decades. AppleScript is a horrendous mess that should never have come into being.
Let's compare.
set x to 0
set taskName to the name of task
var x = 0;
var taskName = task.name();
Trying to make programming languages follow the semantics of English was a horrendous mistake.
Now we have both JSOSA and the osascript
command, scripting Mac applications is a lot easier. You can't directly call JSOSA from Node.js, but you can make a system call to osascript
from your favourite language.
You can experiment with JSOSA in a REPL by running: osascript -l JavaScript -i
You can run JSOSA files with osascript -l JavaScript filename.js
Some general lessons learned so far:
for .. in
doesn't tend to work for the collections returned by apps, butforEach
/filter
-style methods do.- Plenty of things behave differently depending on whether you are getting them as a property vs. calling them. In Things.app, for instance,
Application("Things").lists()
will return a collection of lists, whileApplication("Things").lists
is an object you can call with methods like.byName
. - It would be nice if Apple provided some decent documentation on how AppleScript maps to JavaScript. But they don't.
- One interesting use of JSOSA is you can use it as a way to extract data from Mac apps and store it in more open formats. Basically, the idea is you can build up a JSON output and then use
JSON.stringify
to output it. This is a huge improvement on nasty AppleScript hacks that constructed CSVs by concatenating strings.
var args = $.NSProcessInfo.processInfo.arguments
From this section of the excellent JXA Cookbook
spotify = Application("Spotify");
spotify.pause();
spotify.play();
spotify.nextTrack();
spotify.previousTrack();
/* ^^ this ACTUALLY does the same as pressing 'prev' in the interface. fire it twice to go the previous track. */
itunes = Application("iTunes");
itunes.playpause()
itunes.play();
itunes.pause();
itunes.playerState(); /* paused || stopped || rewinding || fast forwarding */