I'm creating a node.js web app that uses the Spotify Web API and the OpenAI API. The server side code basically takes a prompt from user, processes it with OpenAI language model, then fetches tracks from Spotify and builds a playlist. The app successfully takes the prompt, processes, and fetches the track (I can see the tracks being fetched in my console), but after that I am getting a TypeError: callback is not a function. It appears that the error is being thrown by the http-manager.js file inside the Spotify Web API.
I don't think I even referenced a callback in my code, could this be an issue with Spotify's API/the way I'm trying to interact with it?
This is the exact error in my console when testing the app:
TypeError: callback is not a function
at /rbd/pnpm-volume/bfe9bf90-d68c-4e41-8b2a-95cefb846cfe/node_modules/spotify-web-api-node/src/http-manager.js:71:16
at Request.callback (/rbd/pnpm-volume/bfe9bf90-d68c-4e41-8b2a-95cefb846cfe/node_modules/spotify-web-api-node/node_modules/superagent/lib/node/index.js:905:3)
at /rbd/pnpm-volume/bfe9bf90-d68c-4e41-8b2a-95cefb846cfe/node_modules/spotify-web-api-node/node_modules/superagent/lib/node/index.js:1127:20
at IncomingMessage.<anonymous> (/rbd/pnpm-volume/bfe9bf90-d68c-4e41-8b2a-95cefb846cfe/node_modules/spotify-web-api-node/node_modules/superagent/lib/node/parsers/json.js:22:7)
at Stream.emit (events.js:400:28)
at Unzip.<anonymous> (/rbd/pnpm-volume/bfe9bf90-d68c-4e41-8b2a-95cefb846cfe/node_modules/spotify-web-api-node/node_modules/superagent/lib/node/unzip.js:53:12)
at Unzip.emit (events.js:400:28)
at endReadableNT (internal/streams/readable.js:1334:12)
at processTicksAndRejections (internal/process/task_queues.js:82:21)
server side code:
//variables and imports
//openai api config code....
//fastify configuration code....
// Spotify configuration
const SPOTIFY_CLIENT_ID = process.env.SPOTIFY_CLIENT_ID;
const SPOTIFY_CLIENT_SECRET = process.env.SPOTIFY_CLIENT_SECRET;
const SPOTIFY_REDIRECT_URI = process.env.SPOTIFY_REDIRECT_URI;
const SPOTIFY_AUTH_SCOPES =
'user-read-private user-read-email playlist-modify-public playlist-modify-private';
const SpotifyWebApi = require('spotify-web-api-node');
const spotifyApi = new SpotifyWebApi({
clientId: SPOTIFY_CLIENT_ID,
clientSecret: SPOTIFY_CLIENT_SECRET,
redirectUri: SPOTIFY_REDIRECT_URI,
});
//search and extract songs from Spotify code...
// Utility function to generate a random string
function generateRandomString(length) {
const possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
let text = "";
for (let i = 0; i < length; i++) {
text += possible.charAt(Math.floor(Math.random() * possible.length));
}
return text;
}
//search keyword refining code...
//create playlist and add tracks to it
async function createPlaylistAndAddTracks(userId, playlistName, tracks, accessToken) {
spotifyApi.setAccessToken(accessToken);
try {
const playlist = await new Promise((resolve, reject) => {
spotifyApi.createPlaylist(userId, playlistName, { public: true }, (err, data) => {
if (err) reject(err);
resolve(data);
});
});
const playlistId = playlist.body.id;
const trackUris = tracks.map((track) => track.uri);
await new Promise((resolve, reject) => {
spotifyApi.addTracksToPlaylist(playlistId, trackUris, (err, data) => {
if (err) reject(err);
resolve(data);
});
});
return playlistId;
} catch (error) {
console.error("Error creating playlist and adding tracks:", error);
throw error;
}
}
// Routes
// "/" get route
// "/" post route
fastify.get('/login', (req, reply) => {
const state = generateRandomString(16);
reply.setCookie("spotify_auth_state", state, {
path: "/",
maxAge: 3600, // 1 hour
httpOnly: true,
});
const authUrl =
'https://accounts.spotify.com/authorize' +
'?response_type=code' +
'&client_id=' + encodeURIComponent(SPOTIFY_CLIENT_ID) +
'&scope=' + encodeURIComponent(SPOTIFY_AUTH_SCOPES) +
'&redirect_uri=' + encodeURIComponent(SPOTIFY_REDIRECT_URI) +
'&state=' + state;
reply.redirect(authUrl);
});
// "user" get route code...
//"jockey" route for processing prompts and interacting with Spotify API
fastify.get('/jockey', function (request, reply) {
return reply.view('/src/pages/jockey.hbs');
});
//taking user input and generating keywords for use in SpotifyAPI
fastify.post("/jockey", async function (request, reply) {
const prompt = request.body.prompt;
const promptWithInstruction = `We have a user who wants to listen to music related to the theme: "${prompt}". Can you provide a comma-separated list of keywords or phrases that are relevant to this theme and could be used to search for music on Spotify?`;
try {
const result = await openai.createCompletion({
model: "text-davinci-003",
prompt: promptWithInstruction,
max_tokens: 2048,
temperature: 0.8,
});
const generatedText = result.data.choices[0].text.trim();
const keywords = extractKeywords(generatedText);
console.log("Generated Keywords:", keywords); //MILKSTEAK
const tracks = await searchAndExtractTracks(keywords, request.cookies.access_token);
console.log("Extracted tracks:", tracks);
// Get the user's ID
const userResponse = await spotifyApi.getMe();
const userId = userResponse.body.id;
// Create a new playlist and add the fetched tracks
const playlistId = await createPlaylistAndAddTracks(userId, request.cookies.access_token, tracks);
// Redirect to the /jockey page after processing the input
return reply.redirect("/jockey");
} catch (error) {
console.error(error);
return reply.code(500).send("Error generating response from OpenAI API");
}
});
fastify.get('/callback', async (req, reply) => {
const code = req.query.code;
const state = req.query.state;
const storedState = req.cookies.spotify_auth_state;
if (state === null || state !== storedState) {
reply.code(400).send('State mismatch');
} else {
reply.clearCookie("spotify_auth_state");
const tokenUrl = 'https://accounts.spotify.com/api/token';
try {
const response = await request.post(tokenUrl, {
form: {
code: code,
redirect_uri: SPOTIFY_REDIRECT_URI,
grant_type: 'authorization_code',
},
headers: {
Authorization:
'Basic ' +
Buffer.from(SPOTIFY_CLIENT_ID + ':' + SPOTIFY_CLIENT_SECRET).toString('base64'),
},
json: true,
});
// Save the access_token and refresh_token in cookies
const accessToken = response.access_token;
const refreshToken = response.refresh_token;
reply.setCookie("access_token", accessToken, {
path: "/",
maxAge: 3600, // 1 hour
httpOnly: true,
});
reply.setCookie("refresh_token", refreshToken, {
path: "/",
maxAge: 30 * 24 * 60 * 60, // 30 days
httpOnly: true,
});
reply.redirect('/jockey');
} catch (error) {
reply.code(400).send('Error: ' + error.message);
}
}
});
// let user logout and clear cookies/session "/logout" route
//fastify.listen code...
Try calling createPlaylist
without the userId
argument. Explanation below.
In the changelog of the version 5.0.0 it is said
Create Playlist (createPlaylist) method no longer accepts a userId string as its first argument.
The new definition for that function is createPlaylist(name, options, callback)
and you you are calling it with the userId param, createPlaylist(userId, playlistName, { public: true }, (err, data))
, which means that the function is interpreting your userId
argument as name
, playlistName
as options
, {public: true}
as the callback, and your (err,data)=>{}
callback will of course be ignored, so when that callback will be needed, it will try to execute your {public: true}()
argument thinking that its the callback which of course raises the error stating that callback is not a function
because {public: true}
is indeed not a function.