Powered by AppSignal & Oban Pro
Would you like to see your link here? Contact us

Spotify API (top tracks and now playing)

spotify.livemd

Spotify API (top tracks and now playing)

Mix.install([
  {:req, "~> 0.3.10"},
  {:kino, "~> 0.9.4"}
])

Secrets

spotify_client_id = System.fetch_env!("LB_SPOTIFY_CLIENT_ID")
spotify_client_secret = System.fetch_env!("LB_SPOTIFY_CLIENT_SECRET")
spotify_redirect_uri = System.fetch_env!("LB_SPOTIFY_REDIRECT_URI")
:ok

Spotify module

defmodule SpotifyAPI do
  @scopes ["user-top-read", "user-read-currently-playing"]

  def get_authorization_code(client_id, redirect_uri) do
    Req.get!(
      "https://accounts.spotify.com/authorize",
      params: [
        response_type: "code",
        client_id: client_id,
        redirect_uri: redirect_uri,
        scope: URI.encode(Enum.join(@scopes, " "))
      ],
      follow_redirects: false
    )
  end

  def get_access_token(code, client_id, client_secret, redirect_uri) do
    encoded_token = Base.encode64("#{client_id}:#{client_secret}")

    Req.post!(
      "https://accounts.spotify.com/api/token",
      headers: [
        {"Content-Type", "application/x-www-form-urlencoded"},
        {"Authorization", "Basic #{encoded_token}"}
      ],
      body:
        URI.encode_query(
          grant_type: "authorization_code",
          code: code,
          redirect_uri: redirect_uri
        )
    )
  end

  def refresh_access_token(client_id, client_secret, refresh_token) do
    encoded_token = Base.encode64("#{client_id}:#{client_secret}")

    Req.post!("https://accounts.spotify.com/api/token",
      body: URI.encode_query(grant_type: "refresh_token", refresh_token: refresh_token),
      headers: [
        {"Content-Type", "application/x-www-form-urlencoded"},
        {"Authorization", "Basic #{encoded_token}"}
      ]
    )
  end

  def now_playing(access_token) do
    Req.get!(
      "https://api.spotify.com/v1/me/player/currently-playing",
      headers: [{"Authorization", "Bearer #{access_token}"}]
    )
  end

  def top_tracks(access_token) do
    Req.get!(
      "https://api.spotify.com/v1/me/top/tracks",
      params: [limit: 10, offset: 0],
      headers: [{"Authorization", "Bearer #{access_token}"}]
    )
  end
end
defmodule Spotify do
  def process_currently_playing(track) do
    %{
      name: track["name"],
      album_image_url: get_image_url(track),
      album_name: track["album"]["name"],
      artists: get_artists_name(track)
    }
  end

  def process_top_tracks(tracks) do
    Enum.map(tracks, fn track ->
      %{
        name: track["name"],
        album_image_url: get_image_url(track),
        album_name: track["album"]["name"],
        artists: get_artists_name(track)
      }
    end)
  end

  defp get_image_url(track) do
    images = track["album"]["images"]
    first_image = Enum.at(images, 0)
    first_image["url"]
  end

  defp get_artists_name(track) do
    artists = track["artists"]
    Enum.map(artists, fn artist -> artist["name"] end)
  end
end

Getting Authorization Code

Going to use Authorization Flow to get code

response = SpotifyAPI.get_authorization_code(spotify_client_id, spotify_redirect_uri)

if response.status != 303,
  do: raise("Got response status: #{response.status}")

response_headers = response.headers

{"location", redirect_uri} =
  Enum.find(response_headers, fn {key, _value} -> key == "location" end)

click_html = """


  #{redirect_uri}">Go to consent page

"""

Kino.HTML.new(click_html)

Code input

After granting the consent, you will be redirected to redirect_uri with ?code=code. Set the code below.

code_input = Kino.Input.text("Code")
code = Kino.Input.read(code_input)
:ok

Getting auth token and refresh token

response =
  SpotifyAPI.get_access_token(
    code,
    spotify_client_id,
    spotify_client_secret,
    spotify_redirect_uri
  )

if response.status != 200 do
  IO.puts(response.status)
  IO.puts(inspect(response.body))
  raise("Request failed")
end

token_data = response.body

:ok

Now playing

response = SpotifyAPI.now_playing(token_data["access_token"])

now_playing =
  case response.status do
    200 ->
      {:now_playing, response.body}

    204 ->
      {:not_playing, "Nothing playing at the moment"}

    401 ->
      {:access_token_expired}

    _ ->
      IO.puts(response.status)
      {:error, response}
  end

:ok

Processing now playing

case now_playing do
  {:now_playing, now_playing_response} ->
    Spotify.process_currently_playing(now_playing_response["item"])

  {:not_playing, message} ->
    message

  {:access_token_expired} ->
    IO.puts("Access token expired")

  {:error, error_response} ->
    error_response
end

Top songs

response = SpotifyAPI.top_tracks(token_data["access_token"])

top_tracks_response =
  case response.status do
    200 ->
      {:ok, response.body}

    401 ->
      {:access_token_expired}

    _ ->
      IO.puts(response.status)
      {:error, response}
  end

:ok

Processing top songs

case top_tracks_response do
  {:ok, tracks} -> Spotify.process_top_tracks(tracks["items"])
  {:access_token_expired} -> IO.puts("Access token expired")
  {:error, error_response} -> error_response
end

Refresh access token

refresh_token_response =
  SpotifyAPI.refresh_access_token(
    spotify_client_id,
    spotify_client_secret,
    token_data["refresh_token"]
  )