Skip to content

Instantly share code, notes, and snippets.

@nathanielfernandes
Created December 1, 2022 20:22
Show Gist options
  • Save nathanielfernandes/ca2ccfefb030b1cf56029c301f71743d to your computer and use it in GitHub Desktop.
Save nathanielfernandes/ca2ccfefb030b1cf56029c301f71743d to your computer and use it in GitHub Desktop.
Using a cloudflare worker to get your listening activity from spotify

Using a cloudflare worker to get your listening activity from spotify

In this guide I will explain how to create a cloudflare worker that returns the current song you are listening to on spotify.

You may of seen my use of it on root of my site:
listening

Step 1: Get your Spotify client_id, client_secret and refresh_token

This great guide by Ben Wiz covers exactly this.

make sure you add the scopes user-read-currently-playing and user-read-playback-position

Step 2: Set worker enviroment variables

Once you have the client_id, client_secret, and refresh_token, set the enviroment variable REFRESH_TOKEN with the value being the refresh_token.

Additionally add the enviroment variable CLIENT_ENCODED with the value of <client_id>:<client_secret> base64 encoded.

make sure all you are base64 encoding is the client_id and client_secret seperated by a single colon!

Step 3: Setup cron trigger

The access_token generated from the refresh token is only valid for an hour, so it needs to keep being refreshed. This will be done with a cron trigger.

Add a cron trigger to your cloudflare worker that runs every 30 minutes or */30 * * * *

Step 4: Create a KV name space for the worker to use

The worker needs to hold onto the access_token it generates every 30 minutes.

Create a KV Namespace with the name SPOTIFY and add it to the KV Namespace bindings of your worker.

Located in the settings for your worker, below enviroment variables.

Step 5: Edit your cloudflare worker

Hit the Quick edit button on your cloudflare worker and paste in this:

addEventListener("fetch", event => {
  event.respondWith(handleRequest(event.request).catch(
    (err) => JsonResponse({"error": err})
  ))
})

addEventListener("scheduled", event => {
  event.waitUntil(
    handleSchedule(event.scheduledTime)
  )
})

async function handleSchedule(scheduledDate) {
  await RefreshAccessToken();
}

async function handleRequest(request) {
  const data = await NowPlaying();
  return JsonResponse(data);
}

const REFRESH_URL = "https://accounts.spotify.com/api/token?grant_type=refresh_token&refresh_token=";
const NOW_PLAYING_URL = "https://api.spotify.com/v1/me/player/currently-playing";
const NO_DATA = {
  artist: "",
  artist_url: "",
  url: "",
  track: "",
  image: "",
  is_playing: false,
  progress: 0,
  track_length: 0,
  ok: false,
}

function JsonResponse(data) {
  return new Response(JSON.stringify(data), {
    headers: {
      "Access-Control-Allow-Origin": "*",
      "content-type": "application/json;charset=UTF-8"
    }
  })
}

function ExtractData(json) {
  try {
    const {
      progress_ms: progress = 0,
      item: { 
        album: {
          images: [
            { url: image }
          ]
        },
        artists: [ 
          { 
            name: artist,
            external_urls: {
              spotify: artist_url
            } 
          } 
        ],
        external_urls: { spotify: url },
        duration_ms: track_length,
        name: track,
      },
      is_playing,
    } = json;

    return {
      artist,
      artist_url,
      url,
      track,
      image,
      is_playing,
      progress,
      track_length,
      ok: true,
    }
  } catch (_) {
    return NO_DATA
  }
}

async function RefreshAccessToken() {
  const resp = await fetch(REFRESH_URL + REFRESH_TOKEN, {
    method: 'POST',
    headers: {
      Authorization: `Basic ${CLIENT_ENCODED}`,
     'Content-Type': 'application/x-www-form-urlencoded'
    },
  });

  const { access_token } = await resp.json();
  await SPOTIFY.put("ACCESS_TOKEN", access_token);

  return access_token;
}

async function NowPlaying() {
  const ACCESS_TOKEN = await SPOTIFY.get("ACCESS_TOKEN");
  
  const resp = await fetch(NOW_PLAYING_URL, {
    method: "GET",
    headers: {
      "Authorization": `Bearer ${ACCESS_TOKEN}`,
      "Accept":        "application/json",
      "Content-Type":  "application/json"
    },
  });

  // Status 204 - No Content success
  if (!resp.ok || resp.status === 204) {
    return NO_DATA;
  }

  const json = await resp.json();

  return ExtractData(json);
}

Now make a request to your worker to make sure it works. The code is simple and you can modify/edit the code to better suite your needs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment