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
createPlaylistwithout theuserIdargument. Explanation below.In the changelog of the version 5.0.0 it is said
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 youruserIdargument asname,playlistNameasoptions,{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 thatcallback is not a functionbecause{public: true}is indeed not a function.