go back

make your own spotify listening api

how to use a cloudflare worker to get your listening activity from spotify.

2022-11-03 (2 years ago)

imported from my github gists

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

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.

find me somewhere else on the web