0

Given an album ID (e.g. 1646945378) and a track ID (e.g. 1646945384), is there a way to add a track to the library using AppleScript/JXA?

  • macOS: Sonoma 14.2
  • Music: 1.4.2.83

I know you can do something like

duplicate current track to playlist "Library"

Ideally, I don’t want to play the track before adding it to my library, but I can’t find a way to get a shared track object from just the album and track IDs.

I have figured out how to open the album and select the track:

set albumID to "1646945378"
set trackID to "1646945384"

tell application "Music" to open location "itmss://geo.music.apple.com/album/" & albumID & "?i=" & trackID & "&ls=1&app=music"

However, I can’t get the selected track. This code logs empty lists:

tell application "Music"
  log (get browser window 1's selection)
  log (get selection)
  -- Because an album, not a playlist, is being browsed, ‘get browser 1's view’ doesn’t work either
end tell

As a last resort, I thought I could try manually clicking the add to library button, but I can’t figure out how to tell if a track is selected or not as all of the UI elements seem to have selected:missing value.

tell application "System Events"
  tell process "Music"
    set tracksList to get window 1's splitter group 1's scroll area 2's list 1's list 2
    set trackGroups to tracksList's groups

    -- With the example track above, the first track is selected

    set t1 to get trackGroups's item 1
    set t2 to get trackGroups's item 2

    -- Each group has one child UI element: another group. This group contains stuff like
    -- the track number, name, buttons, etc.
    log (get t1's properties) -- focused:missing value, selected:missing value
    log (get t2's properties) -- focused:missing value, selected:missing value

    log (get t1's group 1's properties) -- focused:false, selected:missing value
    log (get t2's group 1's properties) -- focused:false, selected:missing value
  end tell
end tell

I could use the Apple Music API to get the track number of the track I want to add to my library (or in my specific use-case I wouldn’t even need to use the API to do this) and click the add to library button that way.

However, this all seems quite tedious and annoying for something that I believe should be a builtin AppleScript command. Is there a better/easier way of doing this?


Related:

1 Answer 1

0

Here’s my current slightly hacky solution (JXA) which uses the Apple Music API to get the track number of the track:

const delayAmount = 0.1

// for a synchronous version something like this should work (untested):
/*
const app = Application.currentApplication()
app.includeStandardAdditions = true
const token = '...'
const appleMusicRequest = path => JSON.parse(app.doShellScript(`/usr/bin/curl -f -H authorization:'Bearer ${token}' -H origin:https://amp-api.music.apple.com https://amp-api.music.apple.com/v1/${path}`))
*/
const appleMusicRequest = (() => {
  ObjC.import('Cocoa')

  const token = 'eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IldlYlBsYXlLaWQifQ.eyJpc3MiOiJBTVBXZWJQbGF5IiwiaWF0IjoxNzAyNTAyMjM0LCJleHAiOjE3MDk3NTk4MzQsInJvb3RfaHR0cHNfb3JpZ2luIjpbImFwcGxlLmNvbSJdfQ.zzeMLmez71PLinP9GozYSQnF7NYyCiXHB9tKL3-cyu3LzyeRnYz0ejLj4CrNJs0dlNkFg9_mwKmMLueUAR-KRg'
  const sessionConfig = $.NSURLSessionConfiguration.defaultSessionConfiguration
  sessionConfig.HTTPAdditionalHeaders = $({authorization: `Bearer ${token}`, origin: 'https://amp-api.music.apple.com'})
  const session = $.NSURLSession.sessionWithConfiguration(sessionConfig)
  const baseURL = $.NSURL.URLWithString($('https://amp-api.music.apple.com/v1/'))
  // or if you're part of the Apple Developer Program you can use your own token:
  /*
  const token = 'your developer token'
  const sessionConfig = $.NSURLSessionConfiguration.defaultSessionConfiguration
  sessionConfig.HTTPAdditionalHeaders = $({authorization: `Bearer ${token}`})
  const session = $.NSURLSession.sessionWithConfiguration(sessionConfig)
  const baseURL = $.NSURL.URLWithString($('https://api.music.apple.com/v1/'))
  */

  return async path => new Promise((resolve, reject) =>
    session.dataTaskWithURLCompletionHandler(
      $.NSURL.URLWithStringRelativeToURL($(path), baseURL),
      (data, response, error) => {
        if (!error.isNil()) {
          reject(error)
          return
        }
        const string = $.NSString.alloc.initWithDataEncoding(data, $.NSUTF8StringEncoding).js
        response.statusCode === '200'
          ? resolve(JSON.parse(string))
          : reject(new Error(`Status code ${response.statusCode}: ${string}`))
      }
    ).resume
  )
})()
const appleMusicTrack = async (trackID, storefront = 'au') => (await appleMusicRequest(`catalog/${storefront}/songs/${trackID}`)).data[0]

const Music = Application('Music')
Music.includeStandardAdditions = true

const SystemEvents = Application('System Events')
const musicProcess = SystemEvents.processes.Music

const addTrack = async (albumID, trackID) => {
  Music.openLocation(`itmss://geo.music.apple.com/album/${albumID}?ls=1&app=music`)
  const mainUIElement = musicProcess.windows[0].splitterGroups[0].scrollAreas[1].lists[0]
  while (mainUIElement.lists().length !== 2 || !mainUIElement.lists[1].groups().length) delay(delayAmount)

  const trackContainer = mainUIElement.lists[1]

  const trackNumber = (await appleMusicTrack(trackID)).attributes.trackNumber
  const trackGroup = trackContainer.groups[trackNumber - 1].groups[0]

  trackGroup.buttons.whose({description: 'More'})[0].click()
  while (!trackGroup.menus().length) delay(delayAmount)

  trackGroup.menus[0].menuItems['Add to Library'].click()
}

Hopefully there’s a better way of doing this.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .