@benliam12/spotify-api-helper 1.0.0-DEV.6 → 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,7 +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
+ */
78
84
  getAlbums(albumIds: string[]): Promise<(Album | null)[]>;
85
+ getAlbumTracks(albumId: string, limit?: number, offset?: number): Promise<Track[] | null>;
79
86
  /**
80
87
  * Get a track by ID.
81
88
  * Returns null if the request fails.
@@ -93,10 +100,40 @@ export declare class SpotifyHelper {
93
100
  getPlaylist(playlistId: string): Promise<any | null>;
94
101
  /**
95
102
  * Get available markets.
96
- * Returns empty array if the request fails.
103
+ * @returns Returns empty array if the request fails.
97
104
  */
98
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
+ */
99
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>;
100
137
  /**
101
138
  * Check if the helper is properly authenticated.
102
139
  * Useful for health checks.
@@ -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
  }
@@ -180,19 +180,25 @@ export class SpotifyHelper {
180
180
  name: data.name,
181
181
  album_type: data.album_type,
182
182
  total_tracks: data.total_tracks,
183
- data: data.data
183
+ data: data.data,
184
184
  };
185
185
  return album;
186
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
+ */
187
193
  async getAlbums(albumIds) {
188
- const idsParam = albumIds.join(",");
194
+ const idsParam = albumIds.join(',');
189
195
  if (albumIds.length === 0) {
190
196
  return [];
191
197
  }
192
198
  if (albumIds.length > 20) {
193
- throw new Error("Spotify API allows a maximum of 20 album IDs per request.");
199
+ throw new Error('Spotify API allows a maximum of 20 album IDs per request.');
194
200
  }
195
- const result = await this.makeAuthenticatedRequest(`https://api.spotify.com/v1/albums?ids=${idsParam}`, "getAlbums", { method: "GET" });
201
+ const result = await this.makeAuthenticatedRequest(`https://api.spotify.com/v1/albums?ids=${idsParam}`, 'getAlbums', { method: 'GET' });
196
202
  if (result.success) {
197
203
  return result.data.map((data) => {
198
204
  if (!data) {
@@ -203,19 +209,53 @@ export class SpotifyHelper {
203
209
  name: data.name,
204
210
  album_type: data.album_type,
205
211
  total_tracks: data.total_tracks,
206
- data: data.data
212
+ data: data.data,
207
213
  };
208
214
  return album;
209
215
  });
210
216
  }
211
217
  return [];
212
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
+ }
213
253
  /**
214
254
  * Get a track by ID.
215
255
  * Returns null if the request fails.
216
256
  */
217
257
  async getTrack(trackId) {
218
- 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' });
219
259
  if (!result.success) {
220
260
  return null;
221
261
  }
@@ -226,14 +266,14 @@ export class SpotifyHelper {
226
266
  name: name,
227
267
  album_type: album_type,
228
268
  total_tracks: total_tracks,
229
- data: data
269
+ data: data,
230
270
  };
231
271
  const trackArtistData = result.data.artists.map((artistData) => {
232
272
  const { id, name, ...data } = artistData;
233
273
  const artist = {
234
274
  id: id,
235
275
  name: name,
236
- data: data
276
+ data: data,
237
277
  };
238
278
  return artist;
239
279
  });
@@ -242,7 +282,7 @@ export class SpotifyHelper {
242
282
  name: result.data.name,
243
283
  artists: trackArtistData,
244
284
  album: trackAlbumData,
245
- data: result.data
285
+ data: result.data,
246
286
  };
247
287
  return trackData;
248
288
  }
@@ -252,7 +292,7 @@ export class SpotifyHelper {
252
292
  * Returns null if the request fails.
253
293
  */
254
294
  async getArtist(artistId) {
255
- 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' });
256
296
  if (!result.success) {
257
297
  return null;
258
298
  }
@@ -265,7 +305,7 @@ export class SpotifyHelper {
265
305
  * Returns null if the request fails.
266
306
  */
267
307
  async getPlaylist(playlistId) {
268
- 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' });
269
309
  if (!result.success) {
270
310
  return null;
271
311
  }
@@ -273,17 +313,215 @@ export class SpotifyHelper {
273
313
  }
274
314
  /**
275
315
  * Get available markets.
276
- * Returns empty array if the request fails.
316
+ * @returns Returns empty array if the request fails.
277
317
  */
278
318
  async getAvailableMarkets() {
279
- 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' });
280
320
  if (!result.success) {
281
321
  return [];
282
322
  }
283
323
  return result.data.markets || [];
284
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
+ */
285
333
  async search(query, type, limit = 20, offset = 0) {
286
- 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' });
287
525
  if (!result.success) {
288
526
  return null;
289
527
  }
@@ -316,7 +554,7 @@ export class SpotifyHelper {
316
554
  return {
317
555
  hasToken: this.clientToken !== null,
318
556
  expiresAt: this.clientToken?.expires_at || null,
319
- isExpiringSoon: this.isTokenExpiringSoon()
557
+ isExpiringSoon: this.isTokenExpiringSoon(),
320
558
  };
321
559
  }
322
560
  }
@@ -22,9 +22,37 @@ export interface ClientToken {
22
22
  token_type: string;
23
23
  expires_at: Date;
24
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
+ }
25
32
  export declare enum SearchType {
26
33
  ALBUM = "album",
27
34
  ARTIST = "artist",
28
35
  TRACK = "track",
29
36
  PLAYLIST = "playlist"
30
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.6",
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",