Get Songs from Apple Music API recently added

I'm working on an idea to use recently added tracks in my app.

From what I can tell, the following endpoint will only supply albums and the id's are not related to any catalog resource:

https://api.music.apple.com/v1/me/library/recently-added

However, if I use the following endpoint, I get access to a semi-deeply-nested data value that contains the goodies I'm looking for:

https://api.music.apple.com/v1/me/library/recently-added?include=catalog

This seems better than doing a search for each item, as I have access to a catalog resource id.

The data in the response is like the below.

You can see under relationships, there's catalog, and in there is data, and in there is href, which is what I want.

My problem is that I'm doing something dumb with the the decode.

I'm using MusicKit's MusicDataRequest, as I don't see a way to get this info via MusicKit methods.

And because of that, I gotta setup the request manually.

If I just decode with the following, using Album as a type, the info I want isn't there for understood reasons.

struct AppleMusicRecentlyAddedRequest: Decodable {
    // var next: String?
    var items: [Album]?
    enum CodingKeys: String, CodingKey {
        case next
        case data
    }
    init (from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.items = try container.decodeIfPresent([Album].self, forKey: .data)
    }
}

I've attempted to go down the rabbit hole to get at catalog's data with:

struct AppleMusicRecentlyAddedRequest: Decodable {
    var items: [???]?
        enum CodingKeys: String, CodingKey {
        case next
        case data
        case relationships
        case catalog
    }
    init (from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        let dataContainer = try container.nestedContainer(keyedBy: CodingKeys.self, forKey: .data)
        let relationshipsContainer = try dataContainer.nestedContainer(keyedBy: CodingKeys.self, forKey: .relationships)
        let catalogContainer = try relationshipsContainer.nestedContainer(keyedBy: CodingKeys.self, forKey: .catalog)
        self.items = try catalogContainer.decodeIfPresent([???].self, forKey: .data)
    }
}

But anything I try for ???, including a complete manual copy of the response in a struct, isn't working, mainly because I'm not very good tat this.

The main error I get is:

Expected to decode Dictionary<String, Any> but found an array instead

Which makes me think I gotta decode catalog data, or something, but I'm at my wits end at this point.

Any tips on how to get at data would be awesome; even better would be just to the the href inside catalog/data for each item in the main response.

@JoeKun, if there's anyway that MusicKit can make this easier, kindly advise.

Thanks.

[
    {
    "id":"l.WAiAqm3",
    "type":"library-albums",
    "href":"/v1/me/library/albums/l.WAiAqm3",
    "attributes":{
        "releaseDate":"2010-12-23",
        "artwork":{
            "width":1200,
            "height":1200,
            "url":"https://is4-ssl.mzstatic.com/image/thumb/Music/df/48/bb/mzi.wxowhcmy.jpg/{w}x{h}bb.jpg"
        },
        "dateAdded":"2022-04-13T07:07:50Z",
        "name":"Play With Me Papa",
        "artistName":"John Keawe",
        "genreNames":[
            "New Age"
        ],
        "playParams":{
            "id":"l.WAiAqm3",
            "kind":"album",
            "isLibrary":true
        },
        "trackCount":1
    },
    "relationships":{
        "catalog":{
            "href":"/v1/me/library/albums/l.WAiAqm3/catalog",
            "data":[
                {
                    "id":"417478467",
                    "type":"albums",
                    "href":"/v1/catalog/us/albums/417478467",
                    "attributes":{
                        "artwork":{
                            "width":600,
                            "height":600,
                            "url":"https://is4-ssl.mzstatic.com/image/thumb/Music/df/48/bb/mzi.wxowhcmy.jpg/{w}x{h}bb.jpg",
                            "bgColor":"ecf2fe",
                            "textColor1":"3a2212",
                            "textColor2":"3f2b1b",
                            "textColor3":"5e4b41",
                            "textColor4":"615349"
                        },
                        "artistName":"John Keawe",
                        "isSingle":false,
                        "url":"https://music.apple.com/us/album/play-with-me-papa/417478467",
                        "isComplete":true,
                        "genreNames":[
                            "New Age",
                            "Music",
                            "Worldwide"
                        ],
                        "trackCount":13,
                        "isMasteredForItunes":false,
                        "releaseDate":"2010-12-23",
                        "name":"Play With Me Papa",
                        "recordLabel":"Homestead Productions",
                        "upc":"704565719925",
                        "copyright":"℗ 2010 John Keawe",
                        "playParams":{
                            "id":"417478467",
                            "kind":"album"
                        },
                        "isCompilation":false
                    }
                }
            ]
        }
]
  • From what I understand, dataContainer will be an array and you are expecting a dictionary there

Add a Comment

Replies

While Joe will be able to provide a perfect answer, you can use the following structure to decode the relationship:

struct RecentlyAdded: Decodable {
  var data: [RecentlyAddedData]

  struct RecentlyAddedData: Decodable {
    var relationships: Relationships

    struct Relationships: Decodable {
      var catalog: MusicItemCollection<Album>
    }
  }
}

I am not sure how you can leverage Album and associate the relationship directly with it. Also, if you don't know, the recently-added endpoint provides playlists and stations resources apart from albums.

Looking forward to Joe's answer.

  • Hi @snuff4,

    I finally got back around to this and your struct does work, thanks!

    I do have to do the following to get the albums into a from I can work with of the playlist:

    let albums = items.compactMap{$0.relationships.catalog.compactMap{$0}}.flatMap{$0}

    This is probably because I'm too lazy and dumb on Swift.

    Frankly the whole reason I'm doing this is because of what I think is a bug in the iOS Music app, which I've asked the ever-helpful @JoeKun to check out here.

    And after going through this exercise, I realize that "recently added" isn't going to help, because what I want to do is check recently added for songs that do not exist in certain user library playlists. And since I can only get Albums, followed by subsequent API calls to get Tracks from said Albums, I can only know if there is an intersection of playlist tracks with the recently added album tracks, which lets me ask: "What are the tracks in recently added that are not in this particular playlist", but results in potentially all of the recently added album tracks that I don't care about.

    I wish Recently Added had an option for tracks, and I think I'm not alone with that sentiment.

Add a Comment