DIY Christmas Radio
Tags: go, spotify, sideprojects, webdev
Published: 2020-11-29 17:32:30

Many people who are starting their programming journey often wonder where to get the ideas for their side projects. My advice is that work with a topic you are passionate about. For example, if you love cooking, make yourself a cookbook service or perhaps a digital egg timer. Or if you like to wander in nature, code an app that tracks your routes or helps you to identify which bird is singing. You will have an extra motivational boost when solving the problem will benefit you also otherwise.

"The thing" for me has always been music, so most of my side projects have been related to music in a way or another. One of my favorite apps is Spotify and luckily for me, they have published excellent APIs that one can utilize versatilely in their projects.

---

So it is no surprise that my latest project is also built on Spotify API. As Christmas is getting closer, my daily listening queue is filled with Christmas tunes. However, I tend to listen to the same songs every Christmas. This year I decided to find some new favorites.

We have an excellent internet radio for Christmas songs in Finland called [Jouluradio](https://www.jouluradio.fi/). The only problem when listening to Jouluradio is if you like some new song you hear, saving it for later listening is quite cumbersome. You need to find the track information from the Jouluradio web page and manually search the track from Spotify.

Fortunately, Jouluradio publishes its playlist of the last 20 songs on the service web page. I decided to make "a radio app" that grabs this information. If the tracks are found in Spotify, they are added to my personal Christmas radio playlist and when playing it I can easily save the ones I like for later listening.

---

My radio app, named [Kaneli](https://github.com/lauravuo/kaneli) (cinnamon in Finnish) is a command-line program written in Golang. The trickiest part of the project was to implement the user OAuth 2.0 authentication to Spotify that I wrote about in [my last week's post](https://dev.to/lauravuo/how-to-oauth-from-the-command-line-47j0). The user authentication and authorization part is required for the app to have permission to add tracks to the user-owned Spotify playlist.

Otherwise, the program is mainly about fetching and posting JSON to various endpoints.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/6a9xgtdzt7newf9kf0ya.png)
*API providers and app interaction flow*

---

When the songs' information is acquired from Jouluradio, the app searches from Spotify if the track is found there. The Spotify [search API endpoint](https://developer.spotify.com/documentation/web-api/reference/search/search/) provides handy tools for this.

```go

...

for _, track := range response.PageLastPlayed.Content.RecentlyPlayed.Songs {
  query := url.QueryEscape(fmt.Sprintf("artist:%s track:%s", track.Artist, track.Song))
  // search for song by artist and title
  trackResponse, trackErr := doGetRequest(fmt.Sprintf("https://api.spotify.com/v1/search?type=track&q=%s", query), bearerHeader)
  if trackErr != nil {
    fmt.Printf("Unable to fetch track data %s\n", err.Error())
    continue
  }

  trackData := SpotifyResponse{}
  err = json.Unmarshal(trackResponse, &trackData)
  if err != nil {
    fmt.Printf("Unable to parse track data %s\n", err.Error())
    continue
  }

  // just pick the first found track
  if len(trackData.Tracks.Items) > 0 {
    item := trackData.Tracks.Items[0]
    fmt.Printf("Add track %s: %s\n", track.Artist, track.Song)
    songIds = append(songIds, item.URI)
    removeIds = append(removeIds, &SpotifyRemoveItem{URI: item.URI})
  }
}

...

```

The search query is done for each track using the artist and song title information. If the search returns results, the first found result id is saved for later use.

---

When the looping is done, it's time to add the results to the user's playlist. For this Spotify provides [a playlist API endpoint](https://developer.spotify.com/documentation/web-api/reference/playlists/add-tracks-to-playlist/).

In this phase, I had one problem though: how to prevent the program from adding duplicate tracks. First I tried to find an API that would allow me to search if the playlist contains the specific track already. But luckily, there didn't exist this kind of API. Instead, I figured out a more efficient way of avoiding the duplicates: before adding the items, the program would make a delete request for the tracks that are about to be added. 

```go

...

apiPath := fmt.Sprintf("https://api.spotify.com/v1/playlists/%s/tracks", playlistID)

// first delete all tracks with similar id to avoid duplicates
_, err = doJSONRequest(apiPath, http.MethodDelete, &SpotifyPlaylistDelete{Tracks: removeIds}, bearerHeader)
if err != nil {
  return err
}

// then add all tracks to the start of the list
_, err = doJSONRequest(apiPath, http.MethodPost, &SpotifyPlaylistModify{URIs: songIds, Position: 0}, bearerHeader)

...

```

Both the delete and the add request are done by utilizing batches, so it is more efficient than removing or adding the tracks one-by-one.

---

The codes can be found in [GitHub](https://github.com/lauravuo/kaneli) if you want to take a closer look. To run the app, you need to register your application in [the Spotify developer dashboard](https://developer.spotify.com/dashboard/) and check the further instructions in the repository README.

The current version of the app loops through a couple of different radios dedicated to specific genres and adds the tracks to the user-defined playlist. So far I have been running the program manually at random times but I guess it would be possible to create [a cron job](https://en.wikipedia.org/wiki/Cron) for it to run e.g. once per hour. That is if one would like a really extensive radio list 😄

You can find my Christmas radio [on Spotify](https://open.spotify.com/playlist/5x5mdsVit4ngNyvglqkO8f?si=Qu24MltlRmKBWFoLP_xN0A).

<span>Cover photo by <a href="https://unsplash.com/@markusspiske?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Markus Spiske</a> on <a href="https://unsplash.com/s/photos/fm-radio?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></span>