Home Writing Reading

til / AppleScript - Using JavaScript (JXA)

As we’ve learned in previous posts, AppleScript is a neat and readable language. But, what if we could use a language we’re already familiar with? What if we could use JavaScript? Well, since OS X Yosemite (2014), we can, using “JavaScript for Automation” (JXA).

I haven’t been able to find any official documentation on JXA, but I found helpful information in JXA Resources and JXA Cookbook. From these, I’ve been able to piece together the same example of Spotify’s now playing as we’ve created in the last two posts.

To run JavaScript code in Script Editor, we need to tell it to use JavaScript syntax instead of AppleScript. This can be done by displaying the navigation bar, “View → Show Navigation Bar”, which displays a dropdown where we can select JavaScript. We could also switch the default language inside Script Editor’s settings.

Let’s start by recreating the calculateTime function from the last post.

function calculateTime(totalSeconds) {
  let min = totalSeconds / 60;
  let s = Math.round((min % 1) * 60);
  min = Math.floor(min);

  // Handle if seconds is ever exactly 60
  if (s === 60) {
    s = 0;
    min += 1;
  }

  // Add leading zeros
  if (s < 10) {
    s = `0${s}`;
  }

  return `${min}:${s}`;
}

Other than syntax, there is little difference between the JavaScript and AppleScript version. Note that we have access to JavaScript concepts such as let/const and template strings.

var app = Application("Spotify");
var { artist, name, album, duration } = app.currentTrack();

var currentPosition = calculateTime(app.playerPosition());
var trackDuration = calculateTime(duration() / 1000);

`${artist()} - ${name()} (${album()}) (${currentPosition} / ${trackDuration})`;

Next, we can get the track information. We get a reference to the application (similar to tell application "Spotify"). From this, we can use destructuring to get the individual parts of the current track. We calculate the times and put together a string with all the information. We use var instead of const to avoid errors with duplicate variables when we re-run the script.

The last value is still returned automatically, so we can leave the string at the bottom to use it as our output. The output line needs to start with a semicolon, otherwise it will be interpreted as a tagged template to calculateTime.

The full code is provided below. If we copy this to Script Editor and run it, we’ll get something like "Blue Wednesday - Murmuration (Chillhop Essentials Spring 2019) (0:04 / 2:36)" in the result section.

function calculateTime(totalSeconds) {
  let min = totalSeconds / 60;
  let s = Math.round((min % 1) * 60);
  min = Math.floor(min);

  // Handle if seconds is ever exactly 60
  if (s === 60) {
    s = 0;
    min += 1;
  }

  // Add leading zeros
  if (s < 10) {
    s = `0${s}`;
  }

  return `${min}:${s}`;
}

var app = Application("Spotify");
var { artist, name, album, duration } = app.currentTrack();

var currentPosition = calculateTime(app.playerPosition());
var trackDuration = calculateTime(duration() / 1000);

`${artist()} - ${name()} (${album()}) (${currentPosition} / ${trackDuration})`;

  • Loading next post...
  • Loading previous post...