Archive your Spotify playlists with Deno and GitHub actions

2023-10-12 //

Spotify regularly rotates its playlists, making it easy to discover new music. However, this rotation poses a challenge— songs you enjoyed but forgot to save may disappear. There are, in fact, a lot of people complaining about that on the internet. Especially, when it comes to the Discover Weekly playlist.

Several solutions exist, utilizing proprietary platforms like IFTTT or n8n, some hosted for free by someone else. There is even a public git archive of popular playlists.

Despite these options, I’ve chosen to create my own archiver for the following reasons:

  1. I’ve never worked with the Spotify API before. Taking this opportunity to familiarize myself with it now might prove beneficial for future use.

  2. I’d like to be independent of the continuation of the other solutions, which are run by strangers in their free time. My goal is to avoid dependencies that might suddenly vanish from the internet.

  3. I want to decide how things are archived. This means I want to choose the amount of detail and the data format myself. Opting for popular formats like JSON in a git repository allows for versatile analysis using tools like jq or git log to track playlist changes.

  4. I’d prefer to be able to decide when and where my data is archived, ideally keeping the information about which playlists I have and their content private.

Turns out, nowadays, accomplishing tasks like this doesn’t require owning a server to run programs regularly. If you use GitHub, even with a free account, you get a certain amount of GitHub Actions minutes. This means you can run virtually any software on GitHub’s own machines.

Testing the archiver

Mainly because of reasons 1 and 3, I’ve decided to take matters into my own hands and write the archiving software myself. I’ve chosen the TypeScript/JavaScript runtime Deno. With Deno, I can quickly write and run scripts in an efficient, well-designed, statically-typed language. Goodbye to the days of using Python for these one-off scripts; Deno and TypeScript have become my go-to for these things.

You can find my script in this gist.

To use it, create a Spotify app and save its generated client ID in the SPOTIFY_CLIENT_ID variable and the client secret in the SPOTIFY_CLIENT_SECRET variable. Finally you have to find out your Spotify user name and save it in the SPOTIFY_USER_ID variable. Assuming you have Deno installed—running my gist is a breeze:

mkdir spotify-playlists && cd spotify-playlists
deno run --allow-read --allow-write --allow-env --allow-net https://gist.githubusercontent.com/kmein/94df54ac477f1b1dad07078ae078b67c/raw/spotify-archive.ts

This generates a JSON file for each playlist in your profile, neatly organized into subdirectories by playlist creator. The resulting directory tree might resemble this:

.
├── spotify:user:1148738903
│   └── spotify:playlist:0Zwjm733rLYQmeSQdsgxji.json
├── spotify:user:1152997213
│   └── spotify:playlist:6qDMg5tqmrK6TRhZj9VadO.json
├── spotify:user:1164141596
│   ├── spotify:playlist:05U7KW5SH1m61FqqUGgwvy.json
│   ├── spotify:playlist:6Ay31AW0moa4c3J3fBsN96.json
│   └── spotify:playlist:6vbuAJgHi00bSmUACkYajw.json
├── spotify:user:17a5eqpkkycm0jl6yh7brfm2q
│   └── spotify:playlist:3k1SWmEygS2rnw8jXKL20u.json
└── spotify:user:4k9j8520f791m5ukmi3fw0gnr
    └── spotify:playlist:5Y2tOW9vjqPwmvVsPzYnAS.json

A sample JSON playlist looks like this:

{
  "uri": "spotify:playlist:5Y2tOW9vjqPwFvVsPzYnAS",
  "name": "chill",
  "description": "",
  "image": "https://mosaic.scdn.co/640/ab67616d0000b27345ac58a98431138df3f0300bab67616d0000b2734a72f7ae0082704e32ff2ba3ab67616d0000b2735c27813ae019011fcb370c78ab67616d0000b273dfd4e117451253a318dae489",
  "owner": "spotify:user:4k9j8520f791m5ukhi3fw0gnr",
  "tracks": [
    {
      "uri": "spotify:track:5PCwGjsgwA1AQqHvGmNWX8",
      "name": "Heat Up",
      "added_at": "2020-07-30T11:24:12.000Z",
      "added_by": "spotify:user:4k9j8520f791m5ukhi3fw0gnr",
      "album": "Heat Up",
      "artists": [
        "Giant Rooks"
      ]
    },
    {
      "uri": "spotify:track:3hTydx2QVAE7NZkVvsNOeK",
      "name": "Chapels",
      "added_at": "2020-07-30T11:24:49.000Z",
      "added_by": "spotify:user:4k9j8520f791m5ukhi3fw0gnr",
      "album": "New Estate",
      "artists": [
        "Giant Rooks"
      ]
    },
    // other tracks ...
  ]
}

Running on GitHub actions

Running your own software on GitHub actions is as easy as creating a file .github/workflows/archive.yml in your git repository and pushing it to GitHub. It does not matter whether your remote repository is public or private.

We want to make sure the action runs every day at midnight. This is done using cron syntax. (The workflow_dispatch part enables you to also run the action on button press, not only when it is midnight. This is especially useful if you want to test the archiver script.)

name: Archive
on:
  schedule:
  - cron: "0 0 * * *"
  workflow_dispatch:

Next, we pass our Spotify app’s client ID and client secret to the action. Therefore you have to add the secrets to the repository.

env:
  SPOTIFY_CLIENT_SECRET: ${{ secrets.SPOTIFY_CLIENT_SECRET }}
  SPOTIFY_CLIENT_ID: ${{ secrets.SPOTIFY_CLIENT_ID }}

Now, onto the essential part—the jobs section of the action:

jobs:
  archive:
    environment: Spotify
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v2
    - run: 'rm -r spotify* || :'
    - uses: denoland/setup-deno@v1
      with:
        deno-version: v1.x
    - run: deno run --allow-read --allow-write --allow-env --allow-net https://gist.githubusercontent.com/kmein/94df54ac477f1b1dad07078ae078b67c/raw/spotify-archive.ts
      env:
        SPOTIFY_USER_ID: YOUR_SPOTIFY_USERNAME
    - uses: EndBug/add-and-commit@v7
      with:
        default_author: github_actions
        branch: main

The rm -r command ensures a clean slate for the archiving process, removing previously archived playlists from the action’s working directory. This guarantees that the git repository tracks the deletion of playlists. Their contents remain archived in the repository’s history.

The denoland/setup-deno step sets up a functional Deno environment to run the archiver script from the gist in the next step. Remember to add your Spotify username under the env key.

Lastly, the EndBug/add-and-commit step ensures the directory tree created by the archiver is committed and pushed to your repository on the main branch.

This should leave you with a regular and automatic backup of all the Spotify playlists in your profile. Please let me know if you encounter any trouble. (You can also comment on the gist, of course.)