Lorsque j'ai dévéloppé mon portfolio, j'ai voulu afficher mon écoute Spotify en direct. Et après quelque heure de recherche. J'ai réussi à en voir le bout et voici comment j'ai fait.
Résultat obtenu
Voici le résultat que vous allez obtenir à la fin de cet article.
Spotify
Je suis occupé à écrire de manière claire et concise, la méthode la plus simple pour obtenir vos clés d'API Spotify ainsi que votre TOKEN. Parmi ces éléments, c'est le TOKEN qui peut sembler le plus complexe à récupérer lorsqu'on n'est pas familier avec le processus. Cependant, une fois cette étape accomplie, la récupération devient simple et sans souci.
Environnement
Ajouter dans votre fichier d'environnement les trois variables SPOTIFY_CLIENT_ID
, SPOTIFY_CLIENT_SECRET
et SPOTIFY_REFRESH_TOKEN
.
SPOTIFY_CLIENT_ID=""
SPOTIFY_CLIENT_SECRET=""
SPOTIFY_REFRESH_TOKEN=""
Typage
Nous allons maintenant créer trois fichiers dans src/types/spotify/
nommés entities.d.ts
, entity.d.ts
& request.d.ts
import type { SpotifyEntity, Image } from './entity';
interface Album extends SpotifyEntity {
type: 'album';
popularity: number;
}
interface Artist extends SpotifyEntity {
type: 'artist';
popularity: number;
}
export interface Track extends SpotifyEntity {
type: 'track';
popularity: number;
duration_ms: number;
album: Album;
artists: Array<Artist>;
preview_url: string;
is_playable: boolean;
is_local: boolean;
}
export interface ReadableTrack {
name: string;
artist: string;
album: string;
previewUrl: string;
url: string;
image?: Image;
hdImage?: Image;
duration: number;
}
export type SpotifyEntityType = 'album' | 'artist' | 'playlist' | 'track';
export type SpotifyEntityUri = `spotify:${SpotifyEntityType}:${string}`;
export interface Image {
url: string;
height?: number | null;
width?: number | null;
}
export interface SpotifyEntity {
id: string;
name: string;
href: string;
uri: SpotifyEntityUri;
type: SpotifyEntityType;
images: Array<Image>;
external_urls: { spotify: string };
}
import type { ReadableTrack, Track } from './entities';
import type { SpotifyEntity } from './entity.d';
export interface SpotifyResponse<T extends SpotifyEntity | PlayHistoryObject> {
href: string;
next?: string | null;
previous?: string | null;
limit: number;
offset: number;
total: number;
items: Array<T>;
}
export interface ErrorResponse {
error: {
status: number;
message: string;
};
}
export interface NowPlayingResponse {
is_playing: boolean;
item: Track;
}
export interface PlayHistoryObject {
track: Track;
played_at?: string;
context?: unknown | null;
}
export type NowPlayingAPIResponse = {
track?: ReadableTrack | null;
isPlaying: boolean;
} | null;
Librairie spotify
Nous allons maintenant créer dans src/lib/
un fichier nommé spotify.ts
et mettre toute notre logique métier
dedans.
import type {ErrorResponse, NowPlayingResponse, PlayHistoryObject, SpotifyResponse,} from '@/types/spotify/request.d';
const serialize = (obj: Record<string | number, string | number | boolean>) => {
const str = [];
for (const p in obj) {
if (Object.prototype.hasOwnProperty.call(obj, p)) {
str.push(`${encodeURIComponent(p)}=${encodeURIComponent(obj[p])}`);
}
}
return str.join('&');
};
const buildSpotifyRequest = async <T>(
endpoint: string,
method: 'GET' | 'POST' | 'PUT' | 'DELETE' = 'GET',
body?: Record<string, unknown>,
): Promise<T | ErrorResponse> => {
const {access_token: accessToken} = await getAccessToken().catch(null);
if (!accessToken) {
return {
error: {message: 'Could not get access token', status: 401},
};
}
const response = await fetch(endpoint, {
headers: {
Authorization: `Bearer ${accessToken}`,
},
method,
body: body && method !== 'GET' ? JSON.stringify(body) : undefined,
});
try {
const json = await response.json();
if (response.ok) return json as T;
return json as ErrorResponse;
} catch {
return {
error: {
message: response.statusText || 'Server error',
status: response.status || 500,
},
};
}
};
const clientId = process.env.SPOTIFY_CLIENT_ID || '';
const clientSecret = process.env.SPOTIFY_CLIENT_SECRET || '';
const refreshToken = process.env.SPOTIFY_REFRESH_TOKEN || '';
const basic = btoa(`${clientId}:${clientSecret}`);
const TOKEN_ENDPOINT = 'https://accounts.spotify.com/api/token';
const getAccessToken = async (): Promise<{ access_token?: string }> => {
try {
const response = await fetch(TOKEN_ENDPOINT, {
method: 'POST',
headers: {
Authorization: `Basic ${basic}`,
'Content-Type': 'application/x-www-form-urlencoded',
},
body: serialize({
grant_type: 'refresh_token',
refresh_token: refreshToken,
}),
next: {
revalidate: 0,
},
});
return response.json();
} catch {
return {access_token: undefined};
}
};
const NOW_PLAYING_ENDPOINT = 'https://api.spotify.com/v1/me/player/currently-playing';
export const getNowPlaying = async () => buildSpotifyRequest<NowPlayingResponse>(NOW_PLAYING_ENDPOINT);
const RECENTLY_PLAYED_ENDPOINT = 'https://api.spotify.com/v1/me/player/recently-played?limit=1';
export const getRecentlyPlayed = async () => buildSpotifyRequest<SpotifyResponse<PlayHistoryObject>>(RECENTLY_PLAYED_ENDPOINT,);
Composant
Nous allons maintenant créer un composant et mettre notre logique pour que l'API de Spotify soit call pour mettre à jour la donnée côté client.
import {useRequest} from "@/hooks/use-request";
import type {NowPlayingAPIResponse} from "@/types/spotify/request";
export function CurrentlyListenSpotify() {
const {data} = useRequest<NowPlayingAPIResponse>('api/spotify/now-playing');
const {track} = data || {isPlaying: false};
return (
<div className="space-y-8">
{data?.isPlaying ? (
<a
href={track?.url}
target={'_blank'}
className="select-none"
title={`En train d'écouter ${track?.name} par ${track?.artist} sur Spotify`}
rel="noreferrer"
>
<div className="flex flex-row-reverse items-center justify-between gap-2">
<Image
src={track?.image?.url as string}
alt={`Couverture d'album : '${track?.album}' par '${track?.artist}'`}
width={56}
height={56}
quality={50}
className="size-6 rounded border"
/>
<div className="flex flex-col">
<div className="font-semibold">{track?.artist}</div>
<span className="inline-flex">—</span>
<p className="text-xs text-gray-500">{track?.name}</p>
</div>
</div>
</a>
) : (
<div className="flex flex-row-reverse items-center justify-between gap-2">
<SpotifyIcon />
<div className="flex flex-col">
<div className="font-semibold">Rien n'est écouté</div>
<span className="inline-flex">—</span>
<p className="text-xs text-muted-foreground">Spotify</p></div>
</div>
)}
</div>
)
}
Enfin
Il ne vous reste plus qu'à ajouter votre composant à l'endroit que vous souhaitez l'afficher.
import {CurrentlyListenSpotify} from "@/components/spotify";
export function App () {
return (
<div>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit.</p>
<CurrentlyListenSpotify/>
</div>
)
}