Final commit
This commit is contained in:
parent
91fffb3294
commit
c35e0716af
372 changed files with 16591 additions and 1 deletions
136
backend/spotify-api.ts
Normal file
136
backend/spotify-api.ts
Normal file
|
@ -0,0 +1,136 @@
|
|||
import { assert } from "jsr:@std/assert/assert";
|
||||
import { SpotifySearchResponse, SpotifyTrack } from "./types/spotify.ts";
|
||||
import { SongSearch } from "./types/ai.ts";
|
||||
|
||||
export async function searchSpotifyTracks(
|
||||
songs: SongSearch[],
|
||||
): Promise<SpotifyTrack[]> {
|
||||
const results: SpotifyTrack[] = [];
|
||||
|
||||
const trackPromises = songs.map(async (song) => {
|
||||
try {
|
||||
const query = `track:"${song.title}" artist:"${song.artist}"`;
|
||||
|
||||
const params = new URLSearchParams({
|
||||
q: query,
|
||||
type: "track",
|
||||
market: "US",
|
||||
limit: "1",
|
||||
offset: "0",
|
||||
});
|
||||
|
||||
const url = `https://api.spotify.com/v1/search?${params.toString()}`;
|
||||
|
||||
const response = await fetch(url, {
|
||||
headers: {
|
||||
"Authorization": "Bearer " + await getSpotifyAccessToken(),
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw Error("Unsuccesful response");
|
||||
}
|
||||
|
||||
const data: SpotifySearchResponse = await response.json();
|
||||
|
||||
const track = data.tracks.items.at(0);
|
||||
if (track) {
|
||||
return track;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
|
||||
const tracks = await Promise.all(trackPromises);
|
||||
for (const track of tracks) {
|
||||
if (track) {
|
||||
results.push(track);
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Spotify access token using Client Credentials Flow.
|
||||
* Reads credentials from environment variables, caches token in memory with expiry.
|
||||
* Throws if credentials are missing or request fails.
|
||||
*/
|
||||
let spotifyTokenCache: { token: string; expiresAt: number } | null = null;
|
||||
|
||||
export async function getSpotifyAccessToken(): Promise<string> {
|
||||
const clientId = Deno.env.get("SPOTIFY_CLIENT_ID");
|
||||
const clientSecret = Deno.env.get("SPOTIFY_CLIENT_SECRET");
|
||||
|
||||
if (!clientId || !clientSecret) {
|
||||
throw new Error("Spotify credentials not found in environment variables.");
|
||||
}
|
||||
|
||||
// If cached and not expired, return cached token
|
||||
if (
|
||||
spotifyTokenCache &&
|
||||
spotifyTokenCache.expiresAt > Date.now()
|
||||
) {
|
||||
return spotifyTokenCache.token;
|
||||
}
|
||||
|
||||
const tokenResponse = await fetch("https://accounts.spotify.com/api/token", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
"Authorization": "Basic " +
|
||||
btoa(`${clientId}:${clientSecret}`),
|
||||
},
|
||||
body: "grant_type=client_credentials",
|
||||
});
|
||||
|
||||
if (!tokenResponse.ok) {
|
||||
throw new Error(
|
||||
`Failed to fetch Spotify access token: ${tokenResponse.status} ${await tokenResponse
|
||||
.text()}`,
|
||||
);
|
||||
}
|
||||
|
||||
const tokenData = await tokenResponse.json();
|
||||
const accessToken = tokenData.access_token;
|
||||
const expiresIn = tokenData.expires_in; // seconds
|
||||
|
||||
if (!accessToken || !expiresIn) {
|
||||
throw new Error("Invalid token response from Spotify.");
|
||||
}
|
||||
|
||||
// Cache token with expiry
|
||||
spotifyTokenCache = {
|
||||
token: accessToken,
|
||||
expiresAt: Date.now() + expiresIn * 1000 - 10_000, // 10s early
|
||||
};
|
||||
|
||||
return accessToken;
|
||||
}
|
||||
|
||||
Deno.test("getSpotifyAccessToken caching", async () => {
|
||||
await getSpotifyAccessToken();
|
||||
|
||||
const start2 = performance.now();
|
||||
const token2 = await getSpotifyAccessToken();
|
||||
const elapsed2 = performance.now() - start2;
|
||||
|
||||
assert(elapsed2 < 10, `Second call took too long: ${elapsed2}ms`);
|
||||
});
|
||||
|
||||
Deno.test("searchSpotifyTracks basic", async () => {
|
||||
const songs = [
|
||||
{ title: "Blinding Lights", artist: "The Weeknd" },
|
||||
{ title: "Levitating", artist: "Dua Lipa" },
|
||||
];
|
||||
|
||||
const spotify_api_key = getSpotifyAccessToken();
|
||||
if (!spotify_api_key) throw Error("Testing token not found");
|
||||
const tracks = await searchSpotifyTracks(songs);
|
||||
|
||||
console.log(tracks);
|
||||
|
||||
assert(tracks.length > 0);
|
||||
});
|
Loading…
Add table
Add a link
Reference in a new issue