@benliam12/spotify-api-helper 1.0.0-DEV.5 → 1.0.0-DEV.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,2 +1,48 @@
1
1
  # Spotify API Helper
2
- Help you use the Spotify API without having to implement everything.
2
+
3
+ Help you use the Spotify API without having to implement everything.
4
+
5
+ ## Installation
6
+
7
+ 1. Install this helper using `npm install @benliam12/spotify-api-helper`
8
+ 2. Add the helper to your project.
9
+
10
+ Here is a quick example using NodeJS, and NodeJS, using Module type project.
11
+
12
+ ```javascript
13
+ import express from 'express';
14
+ import dotenv from 'dotenv';
15
+ import {
16
+ SpotifyHelper,
17
+ SpotifyConfiguration,
18
+ } from '@benliam12/spotify-api-helper';
19
+
20
+ dotenv.config();
21
+
22
+ const spotifyConfig = new SpotifyConfiguration({
23
+ clientId: process.env.SPOTIFY_CLIENT_ID,
24
+ clientSecret: process.env.SPOTIFY_CLIENT_SECRET,
25
+ redirectUri: process.env.SPOTIFY_REDIRECT_URI,
26
+ });
27
+
28
+ const spotifyHelperInstance = new SpotifyHelper(spotifyConfig, {
29
+ onError: (error) => {
30
+ console.error('Spotify API Error:', error);
31
+ },
32
+ });
33
+
34
+ app.get('/', async (req, res) => {
35
+ const trackData = await instance.getTrack('YOUR FAVORITE TRACK ID');
36
+ res.send('Hello World!');
37
+ });
38
+
39
+ instance.initialize().then(() => {
40
+ app.listen(port, () => {
41
+ console.log(`Example app listening at http://localhost:${port}`);
42
+ });
43
+ });
44
+ ```
45
+
46
+ ## Contribution
47
+
48
+ Please open issues for any feature request or bug report. As for contributing directly to the project, feel free to make pull requests.
@@ -1,11 +1,11 @@
1
1
  export class SpotifyConfiguration {
2
2
  constructor(config) {
3
- this.clientId = config.clientId ?? "";
4
- this.clientSecret = config.clientSecret ?? "";
5
- this.redirectUri = config.redirectUri ?? "";
3
+ this.clientId = config.clientId ?? '';
4
+ this.clientSecret = config.clientSecret ?? '';
5
+ this.redirectUri = config.redirectUri ?? '';
6
6
  }
7
7
  isValid() {
8
- return this.clientId !== "" && this.clientSecret !== "" && this.redirectUri !== "";
8
+ return this.clientId !== '' && this.clientSecret !== '' && this.redirectUri !== '';
9
9
  }
10
10
  getClientID() {
11
11
  return this.clientId;
@@ -1,5 +1,5 @@
1
- import { SpotifyConfiguration } from "./SpotifyConfiguration.js";
2
- import { Album, Track, Artist, SearchType } from "./SpotifyTypes.js";
1
+ import { SpotifyConfiguration } from './SpotifyConfiguration.js';
2
+ import { Album, Track, Artist, UserToken, SearchType, SpotifyScope } from './SpotifyTypes.js';
3
3
  /**
4
4
  * Result wrapper for operations that can fail
5
5
  */
@@ -75,6 +75,14 @@ export declare class SpotifyHelper {
75
75
  * Returns null if the request fails.
76
76
  */
77
77
  getAlbum(albumId: string): Promise<Album | null>;
78
+ /**
79
+ * Get a list of albums by their IDs.
80
+ * @param albumIds
81
+ * @returns
82
+ * @throws Error if more than 20 IDs are provided (Spotify API limit)
83
+ */
84
+ getAlbums(albumIds: string[]): Promise<(Album | null)[]>;
85
+ getAlbumTracks(albumId: string, limit?: number, offset?: number): Promise<Track[] | null>;
78
86
  /**
79
87
  * Get a track by ID.
80
88
  * Returns null if the request fails.
@@ -92,10 +100,40 @@ export declare class SpotifyHelper {
92
100
  getPlaylist(playlistId: string): Promise<any | null>;
93
101
  /**
94
102
  * Get available markets.
95
- * Returns empty array if the request fails.
103
+ * @returns Returns empty array if the request fails.
96
104
  */
97
105
  getAvailableMarkets(): Promise<string[]>;
106
+ /**
107
+ * Use the search endpoint.
108
+ * @param query
109
+ * @param type
110
+ * @param limit
111
+ * @param offset
112
+ * @returns Parsed search results or null if request fails
113
+ */
98
114
  search(query: string, type: SearchType, limit?: number, offset?: number): Promise<any | null>;
115
+ /** Builds the Spotify authorization URL for user login. */
116
+ getAuthorizationUrl(scopes: SpotifyScope[], state?: string): string;
117
+ /** Exchanges an authorization code for a UserToken. */
118
+ exchangeAuthCode(code: string): Promise<UserToken | null>;
119
+ /** Refreshes an expired user token using its refresh token. */
120
+ refreshUserToken(refreshToken: string): Promise<UserToken | null>;
121
+ /** Makes an API request using a user-provided token. */
122
+ private makeUserRequest;
123
+ /** Get the current user's profile. Requires `user-read-private` scope. */
124
+ getCurrentUserProfile(userToken: UserToken): Promise<any | null>;
125
+ /** Get the current user's playlists. Requires `playlist-read-private` scope. */
126
+ getUserPlaylists(userToken: UserToken, limit?: number, offset?: number): Promise<any | null>;
127
+ /** Get the current user's top artists or tracks. Requires `user-top-read` scope. */
128
+ getUserTopItems(userToken: UserToken, type: 'artists' | 'tracks', limit?: number, offset?: number): Promise<any | null>;
129
+ /** Get the current user's saved tracks. Requires `user-library-read` scope. */
130
+ getUserSavedTracks(userToken: UserToken, limit?: number, offset?: number): Promise<any | null>;
131
+ /**
132
+ * Make Raw request to a given Spotify API URL.
133
+ * @param url
134
+ * @returns Parsed JSON data or null if request fails
135
+ */
136
+ getDataFromURL(url: string): Promise<any | null>;
99
137
  /**
100
138
  * Check if the helper is properly authenticated.
101
139
  * Useful for health checks.
@@ -105,6 +143,10 @@ export declare class SpotifyHelper {
105
143
  * Manually clear the token (useful for testing or forcing a refresh).
106
144
  */
107
145
  clearToken(): void;
146
+ /**
147
+ * Get the current configuration.
148
+ */
149
+ getConfig(): SpotifyConfiguration;
108
150
  /**
109
151
  * Get token info for debugging (without exposing the actual token).
110
152
  */
@@ -6,7 +6,7 @@ export class SpotifyHelper {
6
6
  this.options = {
7
7
  tokenRefreshBufferMs: options.tokenRefreshBufferMs ?? 60000,
8
8
  onError: options.onError ?? (() => { }),
9
- logErrors: options.logErrors ?? false
9
+ logErrors: options.logErrors ?? false,
10
10
  };
11
11
  }
12
12
  /**
@@ -53,7 +53,7 @@ export class SpotifyHelper {
53
53
  const now = new Date();
54
54
  const expiryTime = this.clientToken.expires_at.getTime();
55
55
  const currentTime = now.getTime();
56
- return currentTime >= (expiryTime - this.options.tokenRefreshBufferMs);
56
+ return currentTime >= expiryTime - this.options.tokenRefreshBufferMs;
57
57
  }
58
58
  /**
59
59
  * Fetches a new token from Spotify.
@@ -63,45 +63,45 @@ export class SpotifyHelper {
63
63
  const params = new URLSearchParams();
64
64
  params.append('grant_type', 'client_credentials');
65
65
  try {
66
- const response = await fetch("https://accounts.spotify.com/api/token", {
67
- method: "POST",
66
+ const response = await fetch('https://accounts.spotify.com/api/token', {
67
+ method: 'POST',
68
68
  headers: {
69
- "Content-Type": "application/x-www-form-urlencoded",
70
- "Authorization": "Basic " + Buffer.from(this.config.clientId + ":" + this.config.clientSecret).toString("base64")
69
+ 'Content-Type': 'application/x-www-form-urlencoded',
70
+ Authorization: 'Basic ' + Buffer.from(this.config.clientId + ':' + this.config.clientSecret).toString('base64'),
71
71
  },
72
- body: params.toString()
72
+ body: params.toString(),
73
73
  });
74
74
  if (!response.ok) {
75
75
  const errorText = await response.text();
76
76
  this.handleError({
77
77
  message: `Failed to get token: ${response.status} ${response.statusText} - ${errorText}`,
78
78
  statusCode: response.status,
79
- operation: "fetchNewToken",
80
- timestamp: new Date()
79
+ operation: 'fetchNewToken',
80
+ timestamp: new Date(),
81
81
  });
82
82
  return null;
83
83
  }
84
84
  const data = await response.json();
85
85
  if (!data.access_token || !data.expires_in) {
86
86
  this.handleError({
87
- message: "Invalid token response from Spotify - missing access_token or expires_in",
88
- operation: "fetchNewToken",
89
- timestamp: new Date()
87
+ message: 'Invalid token response from Spotify - missing access_token or expires_in',
88
+ operation: 'fetchNewToken',
89
+ timestamp: new Date(),
90
90
  });
91
91
  return null;
92
92
  }
93
93
  const token = {
94
94
  access_token: data.access_token,
95
95
  token_type: data.token_type,
96
- expires_at: new Date(Date.now() + data.expires_in * 1000)
96
+ expires_at: new Date(Date.now() + data.expires_in * 1000),
97
97
  };
98
98
  return token;
99
99
  }
100
100
  catch (error) {
101
101
  this.handleError({
102
102
  message: `Network error fetching token: ${error instanceof Error ? error.message : String(error)}`,
103
- operation: "fetchNewToken",
104
- timestamp: new Date()
103
+ operation: 'fetchNewToken',
104
+ timestamp: new Date(),
105
105
  });
106
106
  return null;
107
107
  }
@@ -115,7 +115,7 @@ export class SpotifyHelper {
115
115
  if (!token) {
116
116
  return {
117
117
  success: false,
118
- error: "Failed to obtain valid authentication token"
118
+ error: 'Failed to obtain valid authentication token',
119
119
  };
120
120
  }
121
121
  try {
@@ -123,8 +123,8 @@ export class SpotifyHelper {
123
123
  ...options,
124
124
  headers: {
125
125
  ...options.headers,
126
- "Authorization": `Bearer ${token.access_token}`
127
- }
126
+ Authorization: `Bearer ${token.access_token}`,
127
+ },
128
128
  });
129
129
  if (!response.ok) {
130
130
  const errorText = await response.text();
@@ -132,12 +132,12 @@ export class SpotifyHelper {
132
132
  message: `API request failed: ${response.status} ${response.statusText} - ${errorText}`,
133
133
  statusCode: response.status,
134
134
  operation,
135
- timestamp: new Date()
135
+ timestamp: new Date(),
136
136
  });
137
137
  return {
138
138
  success: false,
139
139
  error: `Request failed: ${response.status} ${response.statusText}`,
140
- statusCode: response.status
140
+ statusCode: response.status,
141
141
  };
142
142
  }
143
143
  const data = await response.json();
@@ -148,11 +148,11 @@ export class SpotifyHelper {
148
148
  this.handleError({
149
149
  message: `Network error during ${operation}: ${errorMessage}`,
150
150
  operation,
151
- timestamp: new Date()
151
+ timestamp: new Date(),
152
152
  });
153
153
  return {
154
154
  success: false,
155
- error: `Network error: ${errorMessage}`
155
+ error: `Network error: ${errorMessage}`,
156
156
  };
157
157
  }
158
158
  }
@@ -170,7 +170,7 @@ export class SpotifyHelper {
170
170
  * Returns null if the request fails.
171
171
  */
172
172
  async getAlbum(albumId) {
173
- const result = await this.makeAuthenticatedRequest(`https://api.spotify.com/v1/albums/${albumId}`, "getAlbum", { method: "GET" });
173
+ const result = await this.makeAuthenticatedRequest(`https://api.spotify.com/v1/albums/${albumId}`, 'getAlbum', { method: 'GET' });
174
174
  if (!result.success) {
175
175
  return null;
176
176
  }
@@ -178,32 +178,121 @@ export class SpotifyHelper {
178
178
  const album = {
179
179
  id: data.id,
180
180
  name: data.name,
181
- market: data.market,
182
181
  album_type: data.album_type,
183
182
  total_tracks: data.total_tracks,
184
- data: data
183
+ data: data.data,
185
184
  };
186
185
  return album;
187
186
  }
187
+ /**
188
+ * Get a list of albums by their IDs.
189
+ * @param albumIds
190
+ * @returns
191
+ * @throws Error if more than 20 IDs are provided (Spotify API limit)
192
+ */
193
+ async getAlbums(albumIds) {
194
+ const idsParam = albumIds.join(',');
195
+ if (albumIds.length === 0) {
196
+ return [];
197
+ }
198
+ if (albumIds.length > 20) {
199
+ throw new Error('Spotify API allows a maximum of 20 album IDs per request.');
200
+ }
201
+ const result = await this.makeAuthenticatedRequest(`https://api.spotify.com/v1/albums?ids=${idsParam}`, 'getAlbums', { method: 'GET' });
202
+ if (result.success) {
203
+ return result.data.map((data) => {
204
+ if (!data) {
205
+ return null;
206
+ }
207
+ const album = {
208
+ id: data.id,
209
+ name: data.name,
210
+ album_type: data.album_type,
211
+ total_tracks: data.total_tracks,
212
+ data: data.data,
213
+ };
214
+ return album;
215
+ });
216
+ }
217
+ return [];
218
+ }
219
+ async getAlbumTracks(albumId, limit = 50, offset = 0) {
220
+ if (limit < 1 || limit > 50) {
221
+ throw new Error('Limit must be between 1 and 50');
222
+ }
223
+ const result = await this.makeAuthenticatedRequest(`https://api.spotify.com/v1/albums/${albumId}/tracks?limit=${limit}&offset=${offset}`, 'getAlbumTracks', { method: 'GET' });
224
+ if (!result.success) {
225
+ return null;
226
+ }
227
+ const tracks = result.data.items.map((item) => {
228
+ const track = {
229
+ id: item.id,
230
+ name: item.name,
231
+ artists: item.artists.map((artistData) => {
232
+ const { id, name, ...data } = artistData;
233
+ const artist = {
234
+ id: id,
235
+ name: name,
236
+ data: data,
237
+ };
238
+ return artist;
239
+ }),
240
+ album: {
241
+ id: albumId,
242
+ name: '',
243
+ album_type: '',
244
+ total_tracks: 0,
245
+ data: {},
246
+ },
247
+ data: item,
248
+ };
249
+ return track;
250
+ });
251
+ return tracks;
252
+ }
188
253
  /**
189
254
  * Get a track by ID.
190
255
  * Returns null if the request fails.
191
256
  */
192
257
  async getTrack(trackId) {
193
- const result = await this.makeAuthenticatedRequest(`https://api.spotify.com/v1/tracks/${trackId}`, "getTrack", { method: "GET" });
258
+ const result = await this.makeAuthenticatedRequest(`https://api.spotify.com/v1/tracks/${trackId}`, 'getTrack', { method: 'GET' });
194
259
  if (!result.success) {
195
260
  return null;
196
261
  }
197
- // Map to your Track type based on your type definition
198
- // For now returning null as placeholder
199
- return null;
262
+ else {
263
+ const { id, name, album_type, total_tracks, ...data } = result.data.album;
264
+ const trackAlbumData = {
265
+ id: id,
266
+ name: name,
267
+ album_type: album_type,
268
+ total_tracks: total_tracks,
269
+ data: data,
270
+ };
271
+ const trackArtistData = result.data.artists.map((artistData) => {
272
+ const { id, name, ...data } = artistData;
273
+ const artist = {
274
+ id: id,
275
+ name: name,
276
+ data: data,
277
+ };
278
+ return artist;
279
+ });
280
+ const trackData = {
281
+ id: result.data.id,
282
+ name: result.data.name,
283
+ artists: trackArtistData,
284
+ album: trackAlbumData,
285
+ data: result.data,
286
+ };
287
+ return trackData;
288
+ }
200
289
  }
201
290
  /**
202
291
  * Get an artist by ID.
203
292
  * Returns null if the request fails.
204
293
  */
205
294
  async getArtist(artistId) {
206
- const result = await this.makeAuthenticatedRequest(`https://api.spotify.com/v1/artists/${artistId}`, "getArtist", { method: "GET" });
295
+ const result = await this.makeAuthenticatedRequest(`https://api.spotify.com/v1/artists/${artistId}`, 'getArtist', { method: 'GET' });
207
296
  if (!result.success) {
208
297
  return null;
209
298
  }
@@ -216,7 +305,7 @@ export class SpotifyHelper {
216
305
  * Returns null if the request fails.
217
306
  */
218
307
  async getPlaylist(playlistId) {
219
- const result = await this.makeAuthenticatedRequest(`https://api.spotify.com/v1/playlists/${playlistId}`, "getPlaylist", { method: "GET" });
308
+ const result = await this.makeAuthenticatedRequest(`https://api.spotify.com/v1/playlists/${playlistId}`, 'getPlaylist', { method: 'GET' });
220
309
  if (!result.success) {
221
310
  return null;
222
311
  }
@@ -224,17 +313,215 @@ export class SpotifyHelper {
224
313
  }
225
314
  /**
226
315
  * Get available markets.
227
- * Returns empty array if the request fails.
316
+ * @returns Returns empty array if the request fails.
228
317
  */
229
318
  async getAvailableMarkets() {
230
- const result = await this.makeAuthenticatedRequest(`https://api.spotify.com/v1/markets`, "getAvailableMarkets", { method: "GET" });
319
+ const result = await this.makeAuthenticatedRequest(`https://api.spotify.com/v1/markets`, 'getAvailableMarkets', { method: 'GET' });
231
320
  if (!result.success) {
232
321
  return [];
233
322
  }
234
323
  return result.data.markets || [];
235
324
  }
325
+ /**
326
+ * Use the search endpoint.
327
+ * @param query
328
+ * @param type
329
+ * @param limit
330
+ * @param offset
331
+ * @returns Parsed search results or null if request fails
332
+ */
236
333
  async search(query, type, limit = 20, offset = 0) {
237
- const result = await this.makeAuthenticatedRequest(`https://api.spotify.com/v1/search?q=${encodeURIComponent(query)}&type=${type}&limit=${limit}&offset=${offset}`, "search", { method: "GET" });
334
+ const result = await this.makeAuthenticatedRequest(`https://api.spotify.com/v1/search?q=${encodeURIComponent(query)}&type=${type}&limit=${limit}&offset=${offset}`, 'search', { method: 'GET' });
335
+ if (!result.success) {
336
+ return null;
337
+ }
338
+ return result.data;
339
+ }
340
+ // ─── Authorization Code Flow (User Token) ───
341
+ /** Builds the Spotify authorization URL for user login. */
342
+ getAuthorizationUrl(scopes, state) {
343
+ const params = new URLSearchParams({
344
+ response_type: 'code',
345
+ client_id: this.config.clientId,
346
+ redirect_uri: this.config.redirectUri,
347
+ scope: scopes.join(' '),
348
+ });
349
+ if (state) {
350
+ params.set('state', state);
351
+ }
352
+ return `https://accounts.spotify.com/authorize?${params.toString()}`;
353
+ }
354
+ /** Exchanges an authorization code for a UserToken. */
355
+ async exchangeAuthCode(code) {
356
+ const params = new URLSearchParams({
357
+ grant_type: 'authorization_code',
358
+ code,
359
+ redirect_uri: this.config.redirectUri,
360
+ });
361
+ try {
362
+ const response = await fetch('https://accounts.spotify.com/api/token', {
363
+ method: 'POST',
364
+ headers: {
365
+ 'Content-Type': 'application/x-www-form-urlencoded',
366
+ Authorization: 'Basic ' + Buffer.from(this.config.clientId + ':' + this.config.clientSecret).toString('base64'),
367
+ },
368
+ body: params.toString(),
369
+ });
370
+ if (!response.ok) {
371
+ const errorText = await response.text();
372
+ this.handleError({
373
+ message: `Failed to exchange auth code: ${response.status} ${response.statusText} - ${errorText}`,
374
+ statusCode: response.status,
375
+ operation: 'exchangeAuthCode',
376
+ timestamp: new Date(),
377
+ });
378
+ return null;
379
+ }
380
+ const data = await response.json();
381
+ if (!data.access_token || !data.refresh_token) {
382
+ this.handleError({
383
+ message: 'Invalid token response - missing access_token or refresh_token',
384
+ operation: 'exchangeAuthCode',
385
+ timestamp: new Date(),
386
+ });
387
+ return null;
388
+ }
389
+ return {
390
+ access_token: data.access_token,
391
+ refresh_token: data.refresh_token,
392
+ token_type: data.token_type,
393
+ expires_at: new Date(Date.now() + data.expires_in * 1000),
394
+ scope: data.scope ?? '',
395
+ };
396
+ }
397
+ catch (error) {
398
+ this.handleError({
399
+ message: `Network error exchanging auth code: ${error instanceof Error ? error.message : String(error)}`,
400
+ operation: 'exchangeAuthCode',
401
+ timestamp: new Date(),
402
+ });
403
+ return null;
404
+ }
405
+ }
406
+ /** Refreshes an expired user token using its refresh token. */
407
+ async refreshUserToken(refreshToken) {
408
+ const params = new URLSearchParams({
409
+ grant_type: 'refresh_token',
410
+ refresh_token: refreshToken,
411
+ });
412
+ try {
413
+ const response = await fetch('https://accounts.spotify.com/api/token', {
414
+ method: 'POST',
415
+ headers: {
416
+ 'Content-Type': 'application/x-www-form-urlencoded',
417
+ Authorization: 'Basic ' + Buffer.from(this.config.clientId + ':' + this.config.clientSecret).toString('base64'),
418
+ },
419
+ body: params.toString(),
420
+ });
421
+ if (!response.ok) {
422
+ const errorText = await response.text();
423
+ this.handleError({
424
+ message: `Failed to refresh user token: ${response.status} ${response.statusText} - ${errorText}`,
425
+ statusCode: response.status,
426
+ operation: 'refreshUserToken',
427
+ timestamp: new Date(),
428
+ });
429
+ return null;
430
+ }
431
+ const data = await response.json();
432
+ if (!data.access_token) {
433
+ this.handleError({
434
+ message: 'Invalid refresh response - missing access_token',
435
+ operation: 'refreshUserToken',
436
+ timestamp: new Date(),
437
+ });
438
+ return null;
439
+ }
440
+ return {
441
+ access_token: data.access_token,
442
+ refresh_token: data.refresh_token ?? refreshToken,
443
+ token_type: data.token_type,
444
+ expires_at: new Date(Date.now() + data.expires_in * 1000),
445
+ scope: data.scope ?? '',
446
+ };
447
+ }
448
+ catch (error) {
449
+ this.handleError({
450
+ message: `Network error refreshing user token: ${error instanceof Error ? error.message : String(error)}`,
451
+ operation: 'refreshUserToken',
452
+ timestamp: new Date(),
453
+ });
454
+ return null;
455
+ }
456
+ }
457
+ // ─── User-Scoped API Methods ───
458
+ /** Makes an API request using a user-provided token. */
459
+ async makeUserRequest(url, userToken, operation, options = {}) {
460
+ try {
461
+ const response = await fetch(url, {
462
+ ...options,
463
+ headers: {
464
+ ...options.headers,
465
+ Authorization: `Bearer ${userToken.access_token}`,
466
+ },
467
+ });
468
+ if (!response.ok) {
469
+ const errorText = await response.text();
470
+ this.handleError({
471
+ message: `API request failed: ${response.status} ${response.statusText} - ${errorText}`,
472
+ statusCode: response.status,
473
+ operation,
474
+ timestamp: new Date(),
475
+ });
476
+ return {
477
+ success: false,
478
+ error: `Request failed: ${response.status} ${response.statusText}`,
479
+ statusCode: response.status,
480
+ };
481
+ }
482
+ const data = await response.json();
483
+ return { success: true, data };
484
+ }
485
+ catch (error) {
486
+ const errorMessage = error instanceof Error ? error.message : String(error);
487
+ this.handleError({
488
+ message: `Network error during ${operation}: ${errorMessage}`,
489
+ operation,
490
+ timestamp: new Date(),
491
+ });
492
+ return {
493
+ success: false,
494
+ error: `Network error: ${errorMessage}`,
495
+ };
496
+ }
497
+ }
498
+ /** Get the current user's profile. Requires `user-read-private` scope. */
499
+ async getCurrentUserProfile(userToken) {
500
+ const result = await this.makeUserRequest('https://api.spotify.com/v1/me', userToken, 'getCurrentUserProfile');
501
+ return result.success ? result.data : null;
502
+ }
503
+ /** Get the current user's playlists. Requires `playlist-read-private` scope. */
504
+ async getUserPlaylists(userToken, limit = 20, offset = 0) {
505
+ const result = await this.makeUserRequest(`https://api.spotify.com/v1/me/playlists?limit=${limit}&offset=${offset}`, userToken, 'getUserPlaylists');
506
+ return result.success ? result.data : null;
507
+ }
508
+ /** Get the current user's top artists or tracks. Requires `user-top-read` scope. */
509
+ async getUserTopItems(userToken, type, limit = 20, offset = 0) {
510
+ const result = await this.makeUserRequest(`https://api.spotify.com/v1/me/top/${type}?limit=${limit}&offset=${offset}`, userToken, 'getUserTopItems');
511
+ return result.success ? result.data : null;
512
+ }
513
+ /** Get the current user's saved tracks. Requires `user-library-read` scope. */
514
+ async getUserSavedTracks(userToken, limit = 20, offset = 0) {
515
+ const result = await this.makeUserRequest(`https://api.spotify.com/v1/me/tracks?limit=${limit}&offset=${offset}`, userToken, 'getUserSavedTracks');
516
+ return result.success ? result.data : null;
517
+ }
518
+ /**
519
+ * Make Raw request to a given Spotify API URL.
520
+ * @param url
521
+ * @returns Parsed JSON data or null if request fails
522
+ */
523
+ async getDataFromURL(url) {
524
+ const result = await this.makeAuthenticatedRequest(url, 'getDataFromURL', { method: 'GET' });
238
525
  if (!result.success) {
239
526
  return null;
240
527
  }
@@ -254,6 +541,12 @@ export class SpotifyHelper {
254
541
  clearToken() {
255
542
  this.clientToken = null;
256
543
  }
544
+ /**
545
+ * Get the current configuration.
546
+ */
547
+ getConfig() {
548
+ return this.config;
549
+ }
257
550
  /**
258
551
  * Get token info for debugging (without exposing the actual token).
259
552
  */
@@ -261,7 +554,7 @@ export class SpotifyHelper {
261
554
  return {
262
555
  hasToken: this.clientToken !== null,
263
556
  expiresAt: this.clientToken?.expires_at || null,
264
- isExpiringSoon: this.isTokenExpiringSoon()
557
+ isExpiringSoon: this.isTokenExpiringSoon(),
265
558
  };
266
559
  }
267
560
  }
@@ -1,23 +1,58 @@
1
1
  export interface Album {
2
2
  id: string;
3
3
  name: string;
4
- market: string;
5
4
  album_type: string;
6
5
  total_tracks: number;
7
- data?: any;
6
+ data: any;
8
7
  }
9
8
  export interface Track {
9
+ id: string;
10
+ name: string;
11
+ artists: Artist[];
12
+ album: Album;
13
+ data: any;
10
14
  }
11
15
  export interface Artist {
16
+ id: string;
17
+ name: string;
18
+ data: any;
12
19
  }
13
20
  export interface ClientToken {
14
21
  access_token: string;
15
22
  token_type: string;
16
23
  expires_at: Date;
17
24
  }
25
+ export interface UserToken {
26
+ access_token: string;
27
+ refresh_token: string;
28
+ token_type: string;
29
+ expires_at: Date;
30
+ scope: string;
31
+ }
18
32
  export declare enum SearchType {
19
33
  ALBUM = "album",
20
34
  ARTIST = "artist",
21
35
  TRACK = "track",
22
36
  PLAYLIST = "playlist"
23
37
  }
38
+ export declare enum SpotifyScope {
39
+ UGC_IMAGE_UPLOAD = "ugc-image-upload",
40
+ USER_READ_PLAYBACK_STATE = "user-read-playback-state",
41
+ USER_MODIFY_PLAYBACK_STATE = "user-modify-playback-state",
42
+ USER_READ_CURRENTLY_PLAYING = "user-read-currently-playing",
43
+ STREAMING = "streaming",
44
+ APP_REMOTE_CONTROL = "app-remote-control",
45
+ USER_READ_EMAIL = "user-read-email",
46
+ USER_READ_PRIVATE = "user-read-private",
47
+ PLAYLIST_READ_COLLABORATIVE = "playlist-read-collaborative",
48
+ PLAYLIST_MODIFY_PUBLIC = "playlist-modify-public",
49
+ PLAYLIST_READ_PRIVATE = "playlist-read-private",
50
+ PLAYLIST_MODIFY_PRIVATE = "playlist-modify-private",
51
+ USER_LIBRARY_MODIFY = "user-library-modify",
52
+ USER_LIBRARY_READ = "user-library-read",
53
+ USER_TOP_READ = "user-top-read",
54
+ USER_READ_RECENTLY_PLAYED = "user-read-recently-played",
55
+ USER_READ_PLAYBACK_POSITION = "user-read-playback-position",
56
+ USER_FOLLOW_READ = "user-follow-read",
57
+ USER_FOLLOW_MODIFY = "user-follow-modify"
58
+ }
@@ -5,3 +5,33 @@ export var SearchType;
5
5
  SearchType["TRACK"] = "track";
6
6
  SearchType["PLAYLIST"] = "playlist";
7
7
  })(SearchType || (SearchType = {}));
8
+ export var SpotifyScope;
9
+ (function (SpotifyScope) {
10
+ // Images
11
+ SpotifyScope["UGC_IMAGE_UPLOAD"] = "ugc-image-upload";
12
+ // Spotify Connect
13
+ SpotifyScope["USER_READ_PLAYBACK_STATE"] = "user-read-playback-state";
14
+ SpotifyScope["USER_MODIFY_PLAYBACK_STATE"] = "user-modify-playback-state";
15
+ SpotifyScope["USER_READ_CURRENTLY_PLAYING"] = "user-read-currently-playing";
16
+ // Playback
17
+ SpotifyScope["STREAMING"] = "streaming";
18
+ SpotifyScope["APP_REMOTE_CONTROL"] = "app-remote-control";
19
+ // Users
20
+ SpotifyScope["USER_READ_EMAIL"] = "user-read-email";
21
+ SpotifyScope["USER_READ_PRIVATE"] = "user-read-private";
22
+ // Playlists
23
+ SpotifyScope["PLAYLIST_READ_COLLABORATIVE"] = "playlist-read-collaborative";
24
+ SpotifyScope["PLAYLIST_MODIFY_PUBLIC"] = "playlist-modify-public";
25
+ SpotifyScope["PLAYLIST_READ_PRIVATE"] = "playlist-read-private";
26
+ SpotifyScope["PLAYLIST_MODIFY_PRIVATE"] = "playlist-modify-private";
27
+ // Library
28
+ SpotifyScope["USER_LIBRARY_MODIFY"] = "user-library-modify";
29
+ SpotifyScope["USER_LIBRARY_READ"] = "user-library-read";
30
+ // Listening History
31
+ SpotifyScope["USER_TOP_READ"] = "user-top-read";
32
+ SpotifyScope["USER_READ_RECENTLY_PLAYED"] = "user-read-recently-played";
33
+ SpotifyScope["USER_READ_PLAYBACK_POSITION"] = "user-read-playback-position";
34
+ // Follow
35
+ SpotifyScope["USER_FOLLOW_READ"] = "user-follow-read";
36
+ SpotifyScope["USER_FOLLOW_MODIFY"] = "user-follow-modify";
37
+ })(SpotifyScope || (SpotifyScope = {}));
package/dist/index.d.ts CHANGED
@@ -1,3 +1,3 @@
1
- export * from "./core/SpotifyTypes.js";
2
- export { SpotifyHelper } from "./core/SpotifyHelper.js";
3
- export { SpotifyConfiguration } from "./core/SpotifyConfiguration.js";
1
+ export * from './core/SpotifyTypes.js';
2
+ export { SpotifyHelper } from './core/SpotifyHelper.js';
3
+ export { SpotifyConfiguration } from './core/SpotifyConfiguration.js';
package/dist/index.js CHANGED
@@ -1,3 +1,3 @@
1
- export * from "./core/SpotifyTypes.js";
2
- export { SpotifyHelper } from "./core/SpotifyHelper.js";
3
- export { SpotifyConfiguration } from "./core/SpotifyConfiguration.js";
1
+ export * from './core/SpotifyTypes.js';
2
+ export { SpotifyHelper } from './core/SpotifyHelper.js';
3
+ export { SpotifyConfiguration } from './core/SpotifyConfiguration.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@benliam12/spotify-api-helper",
3
- "version": "1.0.0-DEV.5",
3
+ "version": "1.0.0-DEV.7",
4
4
  "description": "A utility package for Node.js",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -16,8 +16,8 @@
16
16
  "clean": "rimraf dist",
17
17
  "lint": "eslint src --ext .ts",
18
18
  "prepare": "npm run build",
19
- "release": "semantic-release --no-ci",
20
- "release:dry": "semantic-release --dry-run --no-ci"
19
+ "release": "dotenv -e .env -- semantic-release --no-ci --dry-run=false",
20
+ "release:dry": "dotenv -e .env semantic-release --dry-run --no-ci"
21
21
  },
22
22
  "keywords": [
23
23
  "utility",
@@ -38,6 +38,8 @@
38
38
  "@types/node": "^22.19.3",
39
39
  "@typescript-eslint/eslint-plugin": "^8.50.1",
40
40
  "@typescript-eslint/parser": "^8.50.1",
41
+ "conventional-changelog-conventionalcommits": "^9.1.0",
42
+ "dotenv-cli": "^11.0.0",
41
43
  "eslint": "^9.39.2",
42
44
  "jest": "^29.7.0",
43
45
  "rimraf": "^6.1.2",