spotify-api-lib
Version:
A modern, TypeScript-first wrapper for the Spotify Web API with organized endpoint categories and full type safety
1,265 lines (1,256 loc) • 33.4 kB
JavaScript
// src/httpClient.ts
import axios from "axios";
var RETRYABLE_STATUS_CODES = /* @__PURE__ */ new Set([408, 429, 500, 502, 503, 504]);
var SpotifyHttpClient = class {
constructor(accessToken) {
this.accessToken = accessToken || "";
this.client = axios.create({
baseURL: "https://api.spotify.com/v1",
timeout: 1e4,
headers: {
"Content-Type": "application/json"
}
});
this.client.interceptors.request.use((config) => {
if (this.accessToken) {
config.headers.Authorization = `Bearer ${this.accessToken}`;
}
return config;
});
this.client.interceptors.response.use(
(response) => response,
(error) => {
if (error.response?.status === 401) {
console.error("Spotify API: Unauthorized - token may be expired");
} else if (error.response?.status === 429) {
console.error("Spotify API: Rate limited");
} else if (error.response?.status >= 500) {
console.error("Spotify API: Server error");
}
return Promise.reject(error);
}
);
}
/**
* Set the access token for authentication
*/
setAccessToken(token) {
this.accessToken = token;
}
/**
* Clear the access token
*/
clearAccessToken() {
this.accessToken = "";
}
/**
* Make an HTTP request to the Spotify API with improved error handling
*/
async request(endpoint, options) {
const maxRetries = options?.retries ?? 3;
let attempt = 0;
let lastError = null;
while (attempt <= maxRetries) {
try {
const config = {
url: endpoint,
method: options?.method || "GET",
data: options?.data,
params: options?.params,
headers: options?.headers
};
const response = await this.client.request(config);
return response.data;
} catch (error) {
const status = error?.response?.status;
lastError = this.createSpotifyError(error);
if (attempt === 0 && !this.isRetryableError(status)) {
throw lastError;
}
if (status === 429) {
const retryAfter = this.getRetryAfterDelay(error.response.headers?.["retry-after"]);
console.warn(`Spotify API rate limited. Retrying after ${retryAfter}ms (attempt ${attempt + 1}/${maxRetries + 1})`);
await this.delay(retryAfter);
} else if (this.isRetryableError(status)) {
const delay = Math.min(1e3 * Math.pow(2, attempt), 1e4) + Math.random() * 1e3;
console.warn(`Spotify API error ${status}. Retrying after ${delay}ms (attempt ${attempt + 1}/${maxRetries + 1})`);
await this.delay(delay);
} else {
throw lastError;
}
attempt++;
}
}
throw lastError || new Error("Spotify API request failed after all retries");
}
/**
* Check if an HTTP status code should be retried
*/
isRetryableError(status) {
return status ? RETRYABLE_STATUS_CODES.has(status) : false;
}
/**
* Get retry delay from Retry-After header or default
*/
getRetryAfterDelay(retryAfterHeader) {
if (retryAfterHeader) {
const parsed = parseInt(retryAfterHeader, 10);
if (!isNaN(parsed)) {
return parsed * 1e3;
}
}
return 1e3;
}
/**
* Create a standardized error object
*/
createSpotifyError(error) {
const spotifyError = new Error(
error?.response?.data?.error?.message || error?.message || "Spotify API request failed"
);
spotifyError.status = error?.response?.status;
spotifyError.response = error?.response?.data;
return spotifyError;
}
/**
* Promise-based delay utility
*/
delay(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
/**
* Get the underlying axios instance for advanced usage
*/
getClient() {
return this.client;
}
};
// src/baseEndpoint.ts
var BaseEndpoint = class {
constructor(client) {
this.client = client;
}
// Generic request method
async makeRequest(endpoint, options) {
return this.client.request(endpoint, options);
}
// Convenience methods for common HTTP verbs
async get(endpoint, params) {
return this.makeRequest(endpoint, { method: "GET", params });
}
async post(endpoint, data, params) {
return this.makeRequest(endpoint, { method: "POST", data, params });
}
async put(endpoint, data, params) {
return this.makeRequest(endpoint, { method: "PUT", data, params });
}
async delete(endpoint, params) {
return this.makeRequest(endpoint, { method: "DELETE", params });
}
// Standard error handling
handleError(error, operation) {
const message = error?.response?.data?.error?.message || error?.message || "Unknown error";
throw new Error(`${operation} failed: ${message}`);
}
};
// src/endpoints/playlists.ts
var PlaylistEndpoints = class extends BaseEndpoint {
/**
* Get current user's playlists
*/
async getUserPlaylists(options) {
let allPlaylists = [];
let offset = options?.offset || 0;
const limit = options?.limit || 50;
try {
while (true) {
const response = await this.makeRequest("/me/playlists", {
params: { limit, offset }
});
allPlaylists = allPlaylists.concat(response.items);
if (!response.next || response.items.length === 0) {
break;
}
offset += limit;
}
return allPlaylists;
} catch (error) {
console.error("Error fetching playlists:", error);
throw new Error("Failed to fetch playlists");
}
}
/**
* Create a new playlist
*/
async create(name, options) {
try {
const userResponse = await this.makeRequest("/me");
const userId = userResponse.id;
const playlistData = {
name,
description: options?.description || `Created on ${(/* @__PURE__ */ new Date()).toLocaleDateString()}`,
public: options?.public ?? false,
collaborative: options?.collaborative ?? false
};
const response = await this.makeRequest(`/users/${userId}/playlists`, {
method: "POST",
data: playlistData
});
return response;
} catch (error) {
console.error("Error creating playlist:", error);
throw new Error("Failed to create playlist");
}
}
/**
* Add tracks to a playlist
*/
async addTracks(playlistId, trackUris, options) {
try {
const batchSize = 100;
let lastResponse;
for (let i = 0; i < trackUris.length; i += batchSize) {
const batch = trackUris.slice(i, i + batchSize);
const data = { uris: batch };
if (options?.position !== void 0) {
data.position = options.position + i;
}
lastResponse = await this.makeRequest(`/playlists/${playlistId}/tracks`, {
method: "POST",
data
});
}
return lastResponse;
} catch (error) {
console.error("Error adding tracks to playlist:", error);
throw new Error("Failed to add tracks to playlist");
}
}
/**
* Get tracks from a playlist
*/
async getTracks(playlistId, options) {
try {
const params = {
limit: options?.limit || 50,
offset: options?.offset || 0
};
if (options?.fields) {
params.fields = options.fields;
}
const response = await this.makeRequest(`/playlists/${playlistId}/tracks`, {
params
});
return response;
} catch (error) {
console.error("Error fetching playlist tracks:", error);
throw new Error("Failed to fetch playlist tracks");
}
}
/**
* Get a playlist by ID
*/
async getById(playlistId, options) {
try {
const params = {};
if (options?.fields) {
params.fields = options.fields;
}
if (options?.market) {
params.market = options.market;
}
const response = await this.makeRequest(`/playlists/${playlistId}`, {
params
});
return response;
} catch (error) {
console.error("Error fetching playlist:", error);
throw new Error("Failed to fetch playlist");
}
}
/**
* Update playlist details
*/
async update(playlistId, options) {
try {
await this.makeRequest(`/playlists/${playlistId}`, {
method: "PUT",
data: options
});
} catch (error) {
console.error("Error updating playlist:", error);
throw new Error("Failed to update playlist");
}
}
/**
* Remove tracks from a playlist
*/
async removeTracks(playlistId, tracks) {
try {
const response = await this.makeRequest(`/playlists/${playlistId}/tracks`, {
method: "DELETE",
data: { tracks }
});
return response;
} catch (error) {
console.error("Error removing tracks from playlist:", error);
throw new Error("Failed to remove tracks from playlist");
}
}
};
// src/endpoints/tracks.ts
var TrackEndpoints = class extends BaseEndpoint {
/**
* Get user's saved tracks (liked songs)
*/
async getSavedTracks(options) {
const limit = this.validateLimit(options?.limit, 50);
const offset = this.validateOffset(options?.offset);
const params = {
limit,
offset,
...options?.market && { market: options.market }
};
return await this.get("/me/tracks", params);
}
/**
* Validate limit parameter
*/
validateLimit(limit, defaultValue = 20) {
if (!limit) return defaultValue;
return Math.min(Math.max(1, limit), 50);
}
/**
* Validate offset parameter
*/
validateOffset(offset) {
return Math.max(0, offset || 0);
}
/**
* Get albums from user's liked songs
*/
async getLikedSongsAlbums(fetchLimit) {
const albumsMap = /* @__PURE__ */ new Map();
const singlesMap = /* @__PURE__ */ new Map();
let offset = 0;
let fetchedItems = 0;
const limit = 50;
while (!fetchLimit || fetchedItems < fetchLimit) {
const response = await this.get("/me/tracks", { limit, offset });
const items = response.items;
if (items.length === 0) break;
fetchedItems += items.length;
items.forEach((item) => {
if (item.track && item.track.album) {
const album = item.track.album;
if (album.album_type === "single") {
singlesMap.set(album.id, {
id: album.id,
name: album.name,
artists: album.artists,
artistIds: album.artists.map((a) => a.id),
track: item.track.name,
images: album.images,
release_date: album.release_date,
album_type: album.album_type
});
}
if (!albumsMap.has(album.id)) {
albumsMap.set(album.id, {
id: album.id,
name: album.name,
artists: album.artists.map((a) => a.name).join(", "),
release_date: album.release_date,
total_tracks: album.total_tracks,
images: album.images,
album_type: album.album_type
});
}
}
});
offset += limit;
}
return Array.from(albumsMap.values());
}
/**
* Get track by ID
*/
async getById(trackId, options) {
const params = {};
if (options?.market) {
params.market = options.market;
}
return await this.get(`/tracks/${trackId}`, params);
}
/**
* Get multiple tracks by IDs
*/
async getByIds(trackIds, options) {
const params = {
ids: trackIds.join(",")
};
if (options?.market) {
params.market = options.market;
}
return await this.get("/tracks", params);
}
/**
* Save tracks for current user
*/
async saveTracks(trackIds) {
await this.put("/me/tracks", { ids: trackIds });
}
/**
* Remove tracks from current user's saved tracks
*/
async removeTracks(trackIds) {
await this.delete("/me/tracks", { ids: trackIds });
}
/**
* Check if tracks are saved for current user
*/
async checkSavedTracks(trackIds) {
return await this.get("/me/tracks/contains", { ids: trackIds.join(",") });
}
/**
* Get audio features for a track
*/
async getAudioFeatures(trackId) {
return await this.get(`/audio-features/${trackId}`);
}
/**
* Get audio analysis for a track
*/
async getAudioAnalysis(trackId) {
return await this.get(`/audio-analysis/${trackId}`);
}
};
// src/endpoints/albums.ts
var AlbumEndpoints = class extends BaseEndpoint {
/**
* Get album by ID
*/
async getById(albumId, options) {
try {
const params = {};
if (options?.market) {
params.market = options.market;
}
const response = await this.makeRequest(`/albums/${albumId}`, { params });
return response;
} catch (error) {
console.error("Error fetching album:", error);
throw new Error("Failed to fetch album");
}
}
/**
* Get multiple albums by IDs
*/
async getByIds(albumIds, options) {
try {
const params = {
ids: albumIds.join(",")
};
if (options?.market) {
params.market = options.market;
}
const response = await this.makeRequest("/albums", { params });
return response;
} catch (error) {
console.error("Error fetching albums:", error);
throw new Error("Failed to fetch albums");
}
}
/**
* Get album tracks
*/
async getTracks(albumId, options) {
try {
const params = {
limit: options?.limit || 50,
offset: options?.offset || 0,
...options?.market && { market: options.market }
};
const response = await this.makeRequest(`/albums/${albumId}/tracks`, { params });
return response;
} catch (error) {
console.error("Error fetching album tracks:", error);
throw new Error("Failed to fetch album tracks");
}
}
/**
* Get user's saved albums
*/
async getSavedAlbums(options) {
try {
const params = {
limit: options?.limit || 50,
offset: options?.offset || 0,
...options?.market && { market: options.market }
};
const response = await this.makeRequest("/me/albums", { params });
return response;
} catch (error) {
console.error("Error fetching saved albums:", error);
throw new Error("Failed to fetch saved albums");
}
}
/**
* Save albums for current user
*/
async saveAlbums(albumIds) {
try {
await this.makeRequest("/me/albums", {
method: "PUT",
data: { ids: albumIds }
});
} catch (error) {
console.error("Error saving albums:", error);
throw new Error("Failed to save albums");
}
}
/**
* Remove albums from current user's saved albums
*/
async removeAlbums(albumIds) {
try {
await this.makeRequest("/me/albums", {
method: "DELETE",
data: { ids: albumIds }
});
} catch (error) {
console.error("Error removing albums:", error);
throw new Error("Failed to remove albums");
}
}
/**
* Check if albums are saved for current user
*/
async checkSavedAlbums(albumIds) {
try {
const response = await this.makeRequest("/me/albums/contains", {
params: { ids: albumIds.join(",") }
});
return response;
} catch (error) {
console.error("Error checking saved albums:", error);
throw new Error("Failed to check saved albums");
}
}
/**
* Get new album releases
*/
async getNewReleases(options) {
try {
const params = {
limit: options?.limit || 20,
offset: options?.offset || 0,
...options?.country && { country: options.country }
};
const response = await this.makeRequest("/browse/new-releases", { params });
return response.albums;
} catch (error) {
console.error("Error fetching new releases:", error);
throw new Error("Failed to fetch new releases");
}
}
};
// src/endpoints/artists.ts
var ArtistEndpoints = class extends BaseEndpoint {
/**
* Get artist by ID
*/
async getById(artistId) {
try {
const response = await this.makeRequest(`/artists/${artistId}`);
return response;
} catch (error) {
console.error("Error fetching artist:", error);
throw new Error("Failed to fetch artist");
}
}
/**
* Get multiple artists by IDs
*/
async getByIds(artistIds) {
try {
const params = {
ids: artistIds.join(",")
};
const response = await this.makeRequest("/artists", { params });
return response;
} catch (error) {
console.error("Error fetching artists:", error);
throw new Error("Failed to fetch artists");
}
}
/**
* Get artist's albums
*/
async getAlbums(artistId, options) {
try {
const params = {
include_groups: options?.include_groups?.join(",") || "album",
limit: options?.limit || 50,
offset: options?.offset || 0,
...options?.market && { market: options.market }
};
const response = await this.makeRequest(`/artists/${artistId}/albums`, { params });
return response;
} catch (error) {
console.error("Error fetching artist albums:", error);
throw new Error("Failed to fetch artist albums");
}
}
/**
* Get artist's top tracks
*/
async getTopTracks(artistId, options) {
try {
const params = {
market: options?.market || "US"
};
const response = await this.makeRequest(`/artists/${artistId}/top-tracks`, { params });
return response;
} catch (error) {
console.error("Error fetching artist top tracks:", error);
throw new Error("Failed to fetch artist top tracks");
}
}
/**
* Get related artists
*/
async getRelatedArtists(artistId) {
try {
const response = await this.makeRequest(`/artists/${artistId}/related-artists`);
return response;
} catch (error) {
console.error("Error fetching related artists:", error);
throw new Error("Failed to fetch related artists");
}
}
/**
* Follow artists
*/
async follow(artistIds) {
try {
await this.makeRequest("/me/following", {
method: "PUT",
params: { type: "artist" },
data: { ids: artistIds }
});
} catch (error) {
console.error("Error following artists:", error);
throw new Error("Failed to follow artists");
}
}
/**
* Unfollow artists
*/
async unfollow(artistIds) {
try {
await this.makeRequest("/me/following", {
method: "DELETE",
params: { type: "artist" },
data: { ids: artistIds }
});
} catch (error) {
console.error("Error unfollowing artists:", error);
throw new Error("Failed to unfollow artists");
}
}
/**
* Check if user follows artists
*/
async checkFollowing(artistIds) {
try {
const response = await this.makeRequest("/me/following/contains", {
params: {
type: "artist",
ids: artistIds.join(",")
}
});
return response;
} catch (error) {
console.error("Error checking following:", error);
throw new Error("Failed to check following");
}
}
/**
* Get user's followed artists
*/
async getFollowed(options) {
try {
const params = {
type: "artist",
limit: options?.limit || 20,
...options?.after && { after: options.after }
};
const response = await this.makeRequest("/me/following", { params });
return response;
} catch (error) {
console.error("Error fetching followed artists:", error);
throw new Error("Failed to fetch followed artists");
}
}
};
// src/endpoints/search.ts
var SearchEndpoints = class extends BaseEndpoint {
/**
* Search for items
*/
async search(query, options) {
try {
const params = {
q: query,
type: options?.type?.join(",") || "track",
limit: options?.limit || 20,
offset: options?.offset || 0,
...options?.market && { market: options.market },
...options?.include_external && { include_external: options.include_external }
};
const response = await this.makeRequest("/search", { params });
return response;
} catch (error) {
console.error("Error searching:", error);
throw new Error("Failed to search");
}
}
/**
* Search for tracks
*/
async tracks(query, options) {
return this.search(query, {
...options,
type: ["track"]
});
}
/**
* Search for albums
*/
async albums(query, options) {
return this.search(query, {
...options,
type: ["album"]
});
}
/**
* Search for artists
*/
async artists(query, options) {
return this.search(query, {
...options,
type: ["artist"]
});
}
/**
* Search for playlists
*/
async playlists(query, options) {
return this.search(query, {
...options,
type: ["playlist"]
});
}
/**
* Search for shows
*/
async shows(query, options) {
return this.search(query, {
...options,
type: ["show"]
});
}
/**
* Search for episodes
*/
async episodes(query, options) {
return this.search(query, {
...options,
type: ["episode"]
});
}
/**
* Search for audiobooks
*/
async audiobooks(query, options) {
return this.search(query, {
...options,
type: ["audiobook"]
});
}
/**
* Search everything
*/
async all(query, options) {
return this.search(query, {
...options,
type: ["track", "album", "artist", "playlist", "show", "episode", "audiobook"]
});
}
};
// src/endpoints/player.ts
var PlayerEndpoints = class extends BaseEndpoint {
/**
* Get currently playing track
*/
async getCurrentlyPlaying(options) {
try {
const params = {};
if (options?.market) {
params.market = options.market;
}
if (options?.additional_types) {
params.additional_types = options.additional_types.join(",");
}
const response = await this.makeRequest("/me/player/currently-playing", { params });
return response;
} catch (error) {
console.error("Error fetching currently playing:", error);
throw new Error("Failed to fetch currently playing track");
}
}
/**
* Get playback state
*/
async getPlaybackState(options) {
try {
const params = {};
if (options?.market) {
params.market = options.market;
}
if (options?.additional_types) {
params.additional_types = options.additional_types.join(",");
}
const response = await this.makeRequest("/me/player", { params });
return response;
} catch (error) {
console.error("Error fetching playback state:", error);
throw new Error("Failed to fetch playback state");
}
}
/**
* Transfer playback to a device
*/
async transferPlayback(deviceIds, play) {
try {
await this.makeRequest("/me/player", {
method: "PUT",
data: {
device_ids: deviceIds,
play: play ?? false
}
});
} catch (error) {
console.error("Error transferring playback:", error);
throw new Error("Failed to transfer playback");
}
}
/**
* Get available devices
*/
async getDevices() {
try {
const response = await this.makeRequest("/me/player/devices");
return response;
} catch (error) {
console.error("Error fetching devices:", error);
throw new Error("Failed to fetch devices");
}
}
/**
* Start/Resume playback
*/
async play(options) {
try {
const params = {};
if (options?.device_id) {
params.device_id = options.device_id;
}
const data = {};
if (options?.context_uri) {
data.context_uri = options.context_uri;
}
if (options?.uris) {
data.uris = options.uris;
}
if (options?.offset) {
data.offset = options.offset;
}
if (options?.position_ms) {
data.position_ms = options.position_ms;
}
await this.makeRequest("/me/player/play", {
method: "PUT",
params,
data: Object.keys(data).length > 0 ? data : void 0
});
} catch (error) {
console.error("Error starting playback:", error);
throw new Error("Failed to start playback");
}
}
/**
* Pause playback
*/
async pause(device_id) {
try {
const params = {};
if (device_id) {
params.device_id = device_id;
}
await this.makeRequest("/me/player/pause", {
method: "PUT",
params
});
} catch (error) {
console.error("Error pausing playback:", error);
throw new Error("Failed to pause playback");
}
}
/**
* Skip to next track
*/
async next(device_id) {
try {
const params = {};
if (device_id) {
params.device_id = device_id;
}
await this.makeRequest("/me/player/next", {
method: "POST",
params
});
} catch (error) {
console.error("Error skipping to next:", error);
throw new Error("Failed to skip to next track");
}
}
/**
* Skip to previous track
*/
async previous(device_id) {
try {
const params = {};
if (device_id) {
params.device_id = device_id;
}
await this.makeRequest("/me/player/previous", {
method: "POST",
params
});
} catch (error) {
console.error("Error skipping to previous:", error);
throw new Error("Failed to skip to previous track");
}
}
/**
* Seek to position in track
*/
async seek(position_ms, device_id) {
try {
const params = { position_ms };
if (device_id) {
params.device_id = device_id;
}
await this.makeRequest("/me/player/seek", {
method: "PUT",
params
});
} catch (error) {
console.error("Error seeking:", error);
throw new Error("Failed to seek");
}
}
/**
* Set repeat mode
*/
async setRepeat(state, device_id) {
try {
const params = { state };
if (device_id) {
params.device_id = device_id;
}
await this.makeRequest("/me/player/repeat", {
method: "PUT",
params
});
} catch (error) {
console.error("Error setting repeat:", error);
throw new Error("Failed to set repeat mode");
}
}
/**
* Set shuffle mode
*/
async setShuffle(state, device_id) {
try {
const params = { state };
if (device_id) {
params.device_id = device_id;
}
await this.makeRequest("/me/player/shuffle", {
method: "PUT",
params
});
} catch (error) {
console.error("Error setting shuffle:", error);
throw new Error("Failed to set shuffle mode");
}
}
/**
* Set volume
*/
async setVolume(volume_percent, device_id) {
try {
const params = { volume_percent };
if (device_id) {
params.device_id = device_id;
}
await this.makeRequest("/me/player/volume", {
method: "PUT",
params
});
} catch (error) {
console.error("Error setting volume:", error);
throw new Error("Failed to set volume");
}
}
/**
* Add item to playback queue
*/
async addToQueue(uri, device_id) {
try {
const params = { uri };
if (device_id) {
params.device_id = device_id;
}
await this.makeRequest("/me/player/queue", {
method: "POST",
params
});
} catch (error) {
console.error("Error adding to queue:", error);
throw new Error("Failed to add to queue");
}
}
/**
* Get the user's queue
*/
async getQueue() {
try {
const response = await this.makeRequest("/me/player/queue");
return response;
} catch (error) {
console.error("Error fetching queue:", error);
throw new Error("Failed to fetch queue");
}
}
/**
* Get recently played tracks
*/
async getRecentlyPlayed(options) {
try {
const params = {
limit: options?.limit || 20
};
if (options?.after) {
params.after = options.after;
}
if (options?.before) {
params.before = options.before;
}
const response = await this.makeRequest("/me/player/recently-played", { params });
return response;
} catch (error) {
console.error("Error fetching recently played:", error);
throw new Error("Failed to fetch recently played tracks");
}
}
};
// src/endpoints/user.ts
var UserEndpoints = class extends BaseEndpoint {
/**
* Get current user's profile
*/
async getCurrentUser() {
try {
const response = await this.makeRequest("/me");
return response;
} catch (error) {
console.error("Error fetching current user:", error);
throw new Error("Failed to fetch current user");
}
}
/**
* Get user's profile by ID
*/
async getById(userId) {
try {
const response = await this.makeRequest(`/users/${userId}`);
return response;
} catch (error) {
console.error("Error fetching user:", error);
throw new Error("Failed to fetch user");
}
}
/**
* Get user's top items (tracks or artists)
*/
async getTopItems(type, options) {
try {
const params = {
time_range: options?.time_range || "medium_term",
limit: options?.limit || 20,
offset: options?.offset || 0
};
const response = await this.makeRequest(`/me/top/${type}`, { params });
return response;
} catch (error) {
console.error(`Error fetching top ${type}:`, error);
throw new Error(`Failed to fetch top ${type}`);
}
}
/**
* Get user's top tracks
*/
async getTopTracks(options) {
return this.getTopItems("tracks", options);
}
/**
* Get user's top artists
*/
async getTopArtists(options) {
return this.getTopItems("artists", options);
}
/**
* Follow a user
*/
async followUser(userId) {
try {
await this.makeRequest("/me/following", {
method: "PUT",
params: { type: "user" },
data: { ids: [userId] }
});
} catch (error) {
console.error("Error following user:", error);
throw new Error("Failed to follow user");
}
}
/**
* Unfollow a user
*/
async unfollowUser(userId) {
try {
await this.makeRequest("/me/following", {
method: "DELETE",
params: { type: "user" },
data: { ids: [userId] }
});
} catch (error) {
console.error("Error unfollowing user:", error);
throw new Error("Failed to unfollow user");
}
}
/**
* Check if current user follows users
*/
async checkFollowingUsers(userIds) {
try {
const response = await this.makeRequest("/me/following/contains", {
params: {
type: "user",
ids: userIds.join(",")
}
});
return response;
} catch (error) {
console.error("Error checking following users:", error);
throw new Error("Failed to check following users");
}
}
};
// src/main.ts
var ENDPOINT_CLASSES = {
playlists: PlaylistEndpoints,
tracks: TrackEndpoints,
albums: AlbumEndpoints,
artists: ArtistEndpoints,
search: SearchEndpoints,
player: PlayerEndpoints,
user: UserEndpoints
};
var SpotifyApi = class {
constructor(accessToken) {
if (accessToken && !this.isValidTokenFormat(accessToken)) {
console.warn("SpotifyApi: Access token format appears invalid");
}
this.httpClient = new SpotifyHttpClient(accessToken);
this.initializeEndpoints();
}
/**
* Initialize all endpoint groups
*/
initializeEndpoints() {
for (const [name, EndpointClass] of Object.entries(ENDPOINT_CLASSES)) {
;
this[name] = new EndpointClass(this.httpClient);
}
}
/**
* Basic token format validation
*/
isValidTokenFormat(token) {
return typeof token === "string" && token.length > 50 && /^[A-Za-z0-9_-]+$/.test(token);
}
/**
* Set access token for API requests
*/
setAccessToken(token) {
this.httpClient.setAccessToken(token);
}
/**
* Clear the access token
*/
clearAccessToken() {
this.httpClient.clearAccessToken();
}
/**
* Make a raw API request
*/
async request(endpoint, options) {
return this.httpClient.request(endpoint, options);
}
/**
* Get the HTTP client instance for advanced usage
*/
getHttpClient() {
return this.httpClient;
}
/**
* Clean up resources and clear tokens
*/
destroy() {
this.clearAccessToken();
Object.values(ENDPOINT_CLASSES).forEach((_, key) => {
const endpoint = this[Object.keys(ENDPOINT_CLASSES)[key]];
if (endpoint && typeof endpoint.cleanup === "function") {
endpoint.cleanup();
}
});
}
};
export {
AlbumEndpoints,
ArtistEndpoints,
PlayerEndpoints,
PlaylistEndpoints,
SearchEndpoints,
SpotifyApi,
SpotifyHttpClient,
TrackEndpoints,
UserEndpoints,
SpotifyApi as default
};
//# sourceMappingURL=index.mjs.map