@keystrokehq/spotify 0.0.1

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.
Files changed (72) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +113 -0
  3. package/dist/_official/index.d.mts +32 -0
  4. package/dist/_official/index.mjs +3 -0
  5. package/dist/_runtime/index.d.mts +1 -0
  6. package/dist/_runtime/index.mjs +1 -0
  7. package/dist/albums.d.mts +265 -0
  8. package/dist/albums.mjs +72 -0
  9. package/dist/artists.d.mts +237 -0
  10. package/dist/artists.mjs +84 -0
  11. package/dist/audio-analysis.d.mts +91 -0
  12. package/dist/audio-analysis.mjs +19 -0
  13. package/dist/audio-features.d.mts +61 -0
  14. package/dist/audio-features.mjs +31 -0
  15. package/dist/audiobooks.d.mts +176 -0
  16. package/dist/audiobooks.mjs +59 -0
  17. package/dist/browse.d.mts +74 -0
  18. package/dist/browse.mjs +31 -0
  19. package/dist/categories.d.mts +118 -0
  20. package/dist/categories.mjs +66 -0
  21. package/dist/chapters.d.mts +163 -0
  22. package/dist/chapters.mjs +39 -0
  23. package/dist/client.d.mts +3486 -0
  24. package/dist/client.mjs +758 -0
  25. package/dist/connection.d.mts +2 -0
  26. package/dist/connection.mjs +3 -0
  27. package/dist/episodes.d.mts +149 -0
  28. package/dist/episodes.mjs +39 -0
  29. package/dist/errors.d.mts +34 -0
  30. package/dist/errors.mjs +89 -0
  31. package/dist/events.d.mts +613 -0
  32. package/dist/events.mjs +55 -0
  33. package/dist/factory-tZba6Hij.mjs +8 -0
  34. package/dist/follow.d.mts +83 -0
  35. package/dist/follow.mjs +65 -0
  36. package/dist/genres.d.mts +14 -0
  37. package/dist/genres.mjs +19 -0
  38. package/dist/index.d.mts +1 -0
  39. package/dist/index.mjs +1 -0
  40. package/dist/integration-DQ5Egd4f.d.mts +41 -0
  41. package/dist/integration-ykoImsCq.mjs +78 -0
  42. package/dist/library.d.mts +472 -0
  43. package/dist/library.mjs +225 -0
  44. package/dist/markets.d.mts +14 -0
  45. package/dist/markets.mjs +20 -0
  46. package/dist/me.d.mts +169 -0
  47. package/dist/me.mjs +58 -0
  48. package/dist/messaging.d.mts +1 -0
  49. package/dist/messaging.mjs +1 -0
  50. package/dist/operations.d.mts +6 -0
  51. package/dist/operations.mjs +237 -0
  52. package/dist/player.d.mts +877 -0
  53. package/dist/player.mjs +220 -0
  54. package/dist/playlists.d.mts +546 -0
  55. package/dist/playlists.mjs +243 -0
  56. package/dist/recommendations.d.mts +144 -0
  57. package/dist/recommendations.mjs +137 -0
  58. package/dist/schemas.d.mts +3312 -0
  59. package/dist/schemas.mjs +460 -0
  60. package/dist/search.d.mts +389 -0
  61. package/dist/search.mjs +42 -0
  62. package/dist/shows.d.mts +159 -0
  63. package/dist/shows.mjs +59 -0
  64. package/dist/tracks.d.mts +173 -0
  65. package/dist/tracks.mjs +37 -0
  66. package/dist/triggers.d.mts +45 -0
  67. package/dist/triggers.mjs +201 -0
  68. package/dist/users.d.mts +35 -0
  69. package/dist/users.mjs +20 -0
  70. package/dist/verification.d.mts +1 -0
  71. package/dist/verification.mjs +1 -0
  72. package/package.json +187 -0
@@ -0,0 +1,758 @@
1
+ import { SpotifyApiError, classifySpotifyApiError } from "./errors.mjs";
2
+ import { createSpotifyCursorPageSchema, spotifyAlbumPageSchema, spotifyAlbumSchema, spotifyAlbumSummarySchema, spotifyArtistPageSchema, spotifyArtistSchema, spotifyAudioAnalysisSchema, spotifyAudioFeaturesSchema, spotifyAudiobookSchema, spotifyBooleanListSchema, spotifyCategoryPageSchema, spotifyCategorySchema, spotifyChapterPageSchema, spotifyChapterSchema, spotifyDeviceSchema, spotifyEpisodePageSchema, spotifyEpisodeSchema, spotifyMarketSchema, spotifyMutationSuccessSchema, spotifyPlaybackStateSchema, spotifyPlaylistImageSchema, spotifyPlaylistItemPageSchema, spotifyPlaylistItemSchema, spotifyPlaylistPageSchema, spotifyPlaylistSchema, spotifyQueueSchema, spotifyRecentlyPlayedItemSchema, spotifyRecentlyPlayedPageSchema, spotifyRecommendationsSchema, spotifySavedAlbumItemSchema, spotifySavedAlbumPageSchema, spotifySavedAudiobookItemSchema, spotifySavedAudiobookPageSchema, spotifySavedEpisodeItemSchema, spotifySavedEpisodePageSchema, spotifySavedShowItemSchema, spotifySavedShowPageSchema, spotifySavedTrackItemSchema, spotifySavedTrackPageSchema, spotifySearchResultSchema, spotifyShowSchema, spotifySnapshotResponseSchema, spotifyTrackPageSchema, spotifyTrackSchema, spotifyUserSchema } from "./schemas.mjs";
3
+ import { z } from "zod";
4
+
5
+ //#region src/client.ts
6
+ const DEFAULT_BASE_URL = "https://api.spotify.com/v1";
7
+ const DEFAULT_MAX_RETRIES = 2;
8
+ const topTracksResponseSchema = z.object({ tracks: z.array(spotifyTrackSchema) });
9
+ const severalArtistsResponseSchema = z.object({ artists: z.array(spotifyArtistSchema) });
10
+ const severalAlbumsResponseSchema = z.object({ albums: z.array(spotifyAlbumSchema) });
11
+ const severalTracksResponseSchema = z.object({ tracks: z.array(spotifyTrackSchema) });
12
+ const severalShowsResponseSchema = z.object({ shows: z.array(spotifyShowSchema) });
13
+ const severalEpisodesResponseSchema = z.object({ episodes: z.array(spotifyEpisodeSchema) });
14
+ const severalAudiobooksResponseSchema = z.object({ audiobooks: z.array(spotifyAudiobookSchema) });
15
+ const severalChaptersResponseSchema = z.object({ chapters: z.array(spotifyChapterSchema) });
16
+ const nullablePlaybackStateSchema = spotifyPlaybackStateSchema.nullable();
17
+ function isRecord(value) {
18
+ return typeof value === "object" && value !== null;
19
+ }
20
+ function camelCaseKey(value) {
21
+ return value.replace(/_([a-z])/gu, (_, letter) => letter.toUpperCase());
22
+ }
23
+ function snakeCaseKey(value) {
24
+ return value.replace(/[A-Z]/gu, (letter) => `_${letter.toLowerCase()}`);
25
+ }
26
+ function camelizeSpotifyJson(value) {
27
+ if (Array.isArray(value)) return value.map(camelizeSpotifyJson);
28
+ if (!isRecord(value)) return value;
29
+ const result = {};
30
+ for (const [key, nestedValue] of Object.entries(value)) result[camelCaseKey(key)] = camelizeSpotifyJson(nestedValue);
31
+ return result;
32
+ }
33
+ function snakeizeSpotifyJson(value) {
34
+ if (Array.isArray(value)) return value.map(snakeizeSpotifyJson);
35
+ if (!isRecord(value)) return value;
36
+ const result = {};
37
+ for (const [key, nestedValue] of Object.entries(value)) result[snakeCaseKey(key)] = snakeizeSpotifyJson(nestedValue);
38
+ return result;
39
+ }
40
+ function appendQueryParams(url, query) {
41
+ if (!query) return;
42
+ for (const [key, value] of Object.entries(query)) {
43
+ if (value === void 0) continue;
44
+ if (Array.isArray(value)) {
45
+ if (value.length === 0) continue;
46
+ url.searchParams.set(key, value.map((entry) => String(entry)).filter((entry) => entry.length > 0).join(","));
47
+ continue;
48
+ }
49
+ url.searchParams.set(key, String(value));
50
+ }
51
+ }
52
+ function toUrl(baseUrl, path) {
53
+ return new URL(path.replace(/^\//u, ""), `${baseUrl.replace(/\/$/u, "")}/`);
54
+ }
55
+ function parseRetryAfter(headers) {
56
+ const raw = headers.get("retry-after");
57
+ if (!raw) return;
58
+ const seconds = Number(raw);
59
+ return Number.isFinite(seconds) ? seconds : void 0;
60
+ }
61
+ function getRequestId(headers) {
62
+ return headers.get("x-request-id") ?? headers.get("spotify-trace-id") ?? headers.get("x-spotify-request-id") ?? void 0;
63
+ }
64
+ function extractErrorMessage(body, statusText) {
65
+ if (!isRecord(body)) return { message: statusText || "Spotify API request failed" };
66
+ const apiError = body.error;
67
+ if (isRecord(apiError) && typeof apiError.message === "string") return {
68
+ message: apiError.message,
69
+ code: typeof apiError.code === "string" ? apiError.code : void 0
70
+ };
71
+ if (typeof body.error === "string") return {
72
+ message: typeof body.errorDescription === "string" ? body.errorDescription : body.error || statusText,
73
+ code: body.error
74
+ };
75
+ return { message: statusText || "Spotify API request failed" };
76
+ }
77
+ function extractPageToken(url) {
78
+ if (!url) return;
79
+ try {
80
+ const parsed = new URL(url);
81
+ return parsed.searchParams.get("offset") ?? parsed.searchParams.get("after") ?? parsed.searchParams.get("before") ?? url;
82
+ } catch {
83
+ return url;
84
+ }
85
+ }
86
+ function normalizeOffsetPage(value, itemKey, responseSchema) {
87
+ if (!isRecord(value)) return value;
88
+ const itemsValue = value[itemKey];
89
+ const parsedItems = responseSchema.parse(itemsValue);
90
+ return {
91
+ href: typeof value.href === "string" ? value.href : void 0,
92
+ items: parsedItems,
93
+ limit: typeof value.limit === "number" ? value.limit : 0,
94
+ nextPageToken: extractPageToken(typeof value.next === "string" ? value.next : void 0),
95
+ offset: typeof value.offset === "number" ? value.offset : 0,
96
+ previousPageToken: extractPageToken(typeof value.previous === "string" ? value.previous : void 0),
97
+ total: typeof value.total === "number" ? value.total : void 0
98
+ };
99
+ }
100
+ function normalizeCursorPage(value, itemKey, responseSchema) {
101
+ if (!isRecord(value)) return value;
102
+ const itemsValue = value[itemKey];
103
+ const parsedItems = responseSchema.parse(itemsValue);
104
+ const cursors = isRecord(value.cursors) ? value.cursors : void 0;
105
+ return {
106
+ href: typeof value.href === "string" ? value.href : void 0,
107
+ items: parsedItems,
108
+ limit: typeof value.limit === "number" ? value.limit : 0,
109
+ nextPageToken: extractPageToken(typeof value.next === "string" ? value.next : void 0) ?? (typeof cursors?.after === "string" ? cursors.after : void 0),
110
+ previousPageToken: extractPageToken(typeof value.previous === "string" ? value.previous : void 0) ?? (typeof cursors?.before === "string" ? cursors.before : void 0),
111
+ total: typeof value.total === "number" ? value.total : void 0,
112
+ cursors: {
113
+ ...typeof cursors?.after === "string" ? { after: cursors.after } : {},
114
+ ...typeof cursors?.before === "string" ? { before: cursors.before } : {}
115
+ }
116
+ };
117
+ }
118
+ function normalizeEmptySuccess() {
119
+ return { success: true };
120
+ }
121
+ function normalizeEmptyBodyToNull(value) {
122
+ return value === "" ? null : value;
123
+ }
124
+ function toSnakeCaseBody(body) {
125
+ return JSON.stringify(snakeizeSpotifyJson(body));
126
+ }
127
+ function defaultSleep(ms) {
128
+ return new Promise((resolve) => {
129
+ setTimeout(resolve, ms);
130
+ });
131
+ }
132
+ function encodeId(value) {
133
+ return encodeURIComponent(value);
134
+ }
135
+ function createSpotifyClient(credentials, config = {}) {
136
+ const fetchImpl = config.fetch ?? fetch;
137
+ const baseUrl = config.baseUrl ?? DEFAULT_BASE_URL;
138
+ const maxRetries = config.maxRetries ?? DEFAULT_MAX_RETRIES;
139
+ const sleep = config.sleep ?? defaultSleep;
140
+ async function request(options) {
141
+ const url = toUrl(baseUrl, options.path);
142
+ appendQueryParams(url, options.query);
143
+ const method = options.method ?? "GET";
144
+ const requestHeaders = new Headers({
145
+ Authorization: `Bearer ${credentials.SPOTIFY_ACCESS_TOKEN}`,
146
+ Accept: "application/json",
147
+ ...options.headers
148
+ });
149
+ let requestBody;
150
+ if (options.body !== void 0) {
151
+ if (!requestHeaders.has("Content-Type")) requestHeaders.set("Content-Type", "application/json");
152
+ if (typeof options.body === "string") requestBody = options.body;
153
+ else requestBody = toSnakeCaseBody(options.body);
154
+ }
155
+ for (let attempt = 0; attempt <= maxRetries; attempt += 1) {
156
+ const response = await fetchImpl(url, {
157
+ method,
158
+ headers: requestHeaders,
159
+ body: requestBody
160
+ });
161
+ const camelizedBody = camelizeSpotifyJson((response.headers.get("content-type") ?? "").includes("application/json") ? await response.json() : await response.text());
162
+ const retryAfterSeconds = parseRetryAfter(response.headers);
163
+ const requestId = getRequestId(response.headers);
164
+ if (!response.ok) {
165
+ const { message, code } = extractErrorMessage(camelizedBody, response.statusText);
166
+ const error = classifySpotifyApiError({
167
+ status: response.status,
168
+ message,
169
+ code,
170
+ requestId,
171
+ retryAfterSeconds
172
+ });
173
+ if (error.retryable && attempt < maxRetries) {
174
+ await sleep((retryAfterSeconds ?? attempt + 1) * 1e3);
175
+ continue;
176
+ }
177
+ throw error;
178
+ }
179
+ const mapped = options.mapResponse ? options.mapResponse(camelizedBody) : camelizedBody;
180
+ const parsed = options.responseSchema.safeParse(mapped);
181
+ if (!parsed.success) throw new SpotifyApiError({
182
+ kind: "validation",
183
+ message: parsed.error.issues.map((issue) => issue.message).join("; "),
184
+ requestId,
185
+ retryable: false
186
+ });
187
+ return parsed.data;
188
+ }
189
+ throw new SpotifyApiError({
190
+ kind: "rate_limit",
191
+ message: "Spotify API request exhausted retry budget",
192
+ retryable: true
193
+ });
194
+ }
195
+ function getOffsetPage(path, query, itemKey, itemListSchema, pageSchema) {
196
+ return request({
197
+ path,
198
+ query,
199
+ responseSchema: pageSchema,
200
+ mapResponse: (value) => normalizeOffsetPage(value, itemKey, itemListSchema)
201
+ });
202
+ }
203
+ function getCursorPage(path, query, itemKey, itemListSchema, pageSchema) {
204
+ return request({
205
+ path,
206
+ query,
207
+ responseSchema: pageSchema,
208
+ mapResponse: (value) => normalizeCursorPage(value, itemKey, itemListSchema)
209
+ });
210
+ }
211
+ return {
212
+ albums: {
213
+ getAlbum: (id, market) => request({
214
+ path: `/albums/${encodeId(id)}`,
215
+ query: { market },
216
+ responseSchema: spotifyAlbumSchema
217
+ }),
218
+ getAlbumTracks: (id, query) => getOffsetPage(`/albums/${encodeId(id)}/tracks`, query, "items", z.array(spotifyTrackSchema), spotifyTrackPageSchema),
219
+ getSeveralAlbums: (ids, market) => request({
220
+ path: "/albums",
221
+ query: {
222
+ ids,
223
+ market
224
+ },
225
+ responseSchema: severalAlbumsResponseSchema,
226
+ mapResponse: (value) => isRecord(value) ? value : { albums: [] }
227
+ }),
228
+ getNewReleases: (query) => request({
229
+ path: "/browse/new-releases",
230
+ query,
231
+ responseSchema: spotifyAlbumPageSchema,
232
+ mapResponse: (value) => {
233
+ if (!isRecord(value)) return value;
234
+ return normalizeOffsetPage(value.albums, "items", z.array(spotifyAlbumSummarySchema));
235
+ }
236
+ })
237
+ },
238
+ artists: {
239
+ getArtist: (id) => request({
240
+ path: `/artists/${encodeId(id)}`,
241
+ responseSchema: spotifyArtistSchema
242
+ }),
243
+ getArtistAlbums: (id, query) => getOffsetPage(`/artists/${encodeId(id)}/albums`, query, "items", z.array(spotifyAlbumSummarySchema), spotifyAlbumPageSchema),
244
+ getArtistTopTracks: (id, market) => request({
245
+ path: `/artists/${encodeId(id)}/top-tracks`,
246
+ query: { market },
247
+ responseSchema: topTracksResponseSchema,
248
+ mapResponse: (value) => isRecord(value) ? value : { tracks: [] }
249
+ }),
250
+ getArtistRelatedArtists: (id) => request({
251
+ path: `/artists/${encodeId(id)}/related-artists`,
252
+ responseSchema: severalArtistsResponseSchema,
253
+ mapResponse: (value) => isRecord(value) ? value : { artists: [] }
254
+ }),
255
+ getSeveralArtists: (ids) => request({
256
+ path: "/artists",
257
+ query: { ids },
258
+ responseSchema: severalArtistsResponseSchema,
259
+ mapResponse: (value) => isRecord(value) ? value : { artists: [] }
260
+ })
261
+ },
262
+ tracks: {
263
+ getTrack: (id, market) => request({
264
+ path: `/tracks/${encodeId(id)}`,
265
+ query: { market },
266
+ responseSchema: spotifyTrackSchema
267
+ }),
268
+ getSeveralTracks: (ids, market) => request({
269
+ path: "/tracks",
270
+ query: {
271
+ ids,
272
+ market
273
+ },
274
+ responseSchema: severalTracksResponseSchema,
275
+ mapResponse: (value) => isRecord(value) ? value : { tracks: [] }
276
+ })
277
+ },
278
+ audioFeatures: {
279
+ getAudioFeatures: (id) => request({
280
+ path: `/audio-features/${encodeId(id)}`,
281
+ responseSchema: spotifyAudioFeaturesSchema
282
+ }),
283
+ getSeveralAudioFeatures: (ids) => request({
284
+ path: "/audio-features",
285
+ query: { ids },
286
+ responseSchema: z.object({ audioFeatures: z.array(spotifyAudioFeaturesSchema.nullable()) })
287
+ })
288
+ },
289
+ audioAnalysis: { getAudioAnalysis: (id) => request({
290
+ path: `/audio-analysis/${encodeId(id)}`,
291
+ responseSchema: spotifyAudioAnalysisSchema
292
+ }) },
293
+ recommendations: { getRecommendations: (query) => request({
294
+ path: "/recommendations",
295
+ query,
296
+ responseSchema: spotifyRecommendationsSchema
297
+ }) },
298
+ shows: {
299
+ getShow: (id, market) => request({
300
+ path: `/shows/${encodeId(id)}`,
301
+ query: { market },
302
+ responseSchema: spotifyShowSchema
303
+ }),
304
+ getSeveralShows: (ids, market) => request({
305
+ path: "/shows",
306
+ query: {
307
+ ids,
308
+ market
309
+ },
310
+ responseSchema: severalShowsResponseSchema,
311
+ mapResponse: (value) => isRecord(value) ? value : { shows: [] }
312
+ }),
313
+ getShowEpisodes: (id, query) => getOffsetPage(`/shows/${encodeId(id)}/episodes`, query, "items", z.array(spotifyEpisodeSchema), spotifyEpisodePageSchema)
314
+ },
315
+ episodes: {
316
+ getEpisode: (id, market) => request({
317
+ path: `/episodes/${encodeId(id)}`,
318
+ query: { market },
319
+ responseSchema: spotifyEpisodeSchema
320
+ }),
321
+ getSeveralEpisodes: (ids, market) => request({
322
+ path: "/episodes",
323
+ query: {
324
+ ids,
325
+ market
326
+ },
327
+ responseSchema: severalEpisodesResponseSchema,
328
+ mapResponse: (value) => isRecord(value) ? value : { episodes: [] }
329
+ })
330
+ },
331
+ audiobooks: {
332
+ getAudiobook: (id, market) => request({
333
+ path: `/audiobooks/${encodeId(id)}`,
334
+ query: { market },
335
+ responseSchema: spotifyAudiobookSchema
336
+ }),
337
+ getSeveralAudiobooks: (ids, market) => request({
338
+ path: "/audiobooks",
339
+ query: {
340
+ ids,
341
+ market
342
+ },
343
+ responseSchema: severalAudiobooksResponseSchema,
344
+ mapResponse: (value) => isRecord(value) ? value : { audiobooks: [] }
345
+ }),
346
+ getAudiobookChapters: (id, query) => getOffsetPage(`/audiobooks/${encodeId(id)}/chapters`, query, "items", z.array(spotifyChapterSchema), spotifyChapterPageSchema)
347
+ },
348
+ chapters: {
349
+ getChapter: (id, market) => request({
350
+ path: `/chapters/${encodeId(id)}`,
351
+ query: { market },
352
+ responseSchema: spotifyChapterSchema
353
+ }),
354
+ getSeveralChapters: (ids, market) => request({
355
+ path: "/chapters",
356
+ query: {
357
+ ids,
358
+ market
359
+ },
360
+ responseSchema: severalChaptersResponseSchema,
361
+ mapResponse: (value) => isRecord(value) ? value : { chapters: [] }
362
+ })
363
+ },
364
+ discovery: {
365
+ search: (query) => request({
366
+ path: "/search",
367
+ query,
368
+ responseSchema: spotifySearchResultSchema
369
+ }),
370
+ getAvailableMarkets: () => request({
371
+ path: "/markets",
372
+ responseSchema: z.object({ markets: z.array(spotifyMarketSchema) })
373
+ }),
374
+ getAvailableGenreSeeds: () => request({
375
+ path: "/recommendations/available-genre-seeds",
376
+ responseSchema: z.object({ genres: z.array(z.string().min(1)) })
377
+ }),
378
+ getBrowseCategories: (query) => request({
379
+ path: "/browse/categories",
380
+ query,
381
+ responseSchema: spotifyCategoryPageSchema,
382
+ mapResponse: (value) => {
383
+ if (!isRecord(value)) return value;
384
+ return normalizeOffsetPage(value.categories, "items", z.array(spotifyCategorySchema));
385
+ }
386
+ }),
387
+ getBrowseCategory: (id, query) => request({
388
+ path: `/browse/categories/${encodeId(id)}`,
389
+ query,
390
+ responseSchema: spotifyCategorySchema
391
+ }),
392
+ getCategoryPlaylists: (id, query) => request({
393
+ path: `/browse/categories/${encodeId(id)}/playlists`,
394
+ query,
395
+ responseSchema: spotifyPlaylistPageSchema,
396
+ mapResponse: (value) => {
397
+ if (!isRecord(value)) return value;
398
+ return normalizeOffsetPage(value.playlists, "items", z.array(spotifyPlaylistSchema));
399
+ }
400
+ }),
401
+ getFeaturedPlaylists: (query) => request({
402
+ path: "/browse/featured-playlists",
403
+ query,
404
+ responseSchema: spotifyPlaylistPageSchema,
405
+ mapResponse: (value) => {
406
+ if (!isRecord(value)) return value;
407
+ return normalizeOffsetPage(value.playlists, "items", z.array(spotifyPlaylistSchema));
408
+ }
409
+ })
410
+ },
411
+ playlists: {
412
+ createPlaylist: (body) => request({
413
+ method: "POST",
414
+ path: "/me/playlists",
415
+ body,
416
+ responseSchema: spotifyPlaylistSchema
417
+ }),
418
+ getPlaylist: (id, query) => request({
419
+ path: `/playlists/${encodeId(id)}`,
420
+ query,
421
+ responseSchema: spotifyPlaylistSchema
422
+ }),
423
+ updatePlaylistDetails: (id, body) => request({
424
+ method: "PUT",
425
+ path: `/playlists/${encodeId(id)}`,
426
+ body,
427
+ responseSchema: spotifyMutationSuccessSchema,
428
+ mapResponse: normalizeEmptySuccess
429
+ }),
430
+ getPlaylistItems: (id, query) => getOffsetPage(`/playlists/${encodeId(id)}/tracks`, query, "items", z.array(spotifyPlaylistItemSchema), spotifyPlaylistItemPageSchema),
431
+ getPlaylistCoverImage: (id) => request({
432
+ path: `/playlists/${encodeId(id)}/images`,
433
+ responseSchema: z.object({ images: z.array(spotifyPlaylistImageSchema) }),
434
+ mapResponse: (value) => ({ images: value })
435
+ }),
436
+ addItemsToPlaylist: (id, body) => request({
437
+ method: "POST",
438
+ path: `/playlists/${encodeId(id)}/tracks`,
439
+ body,
440
+ responseSchema: spotifySnapshotResponseSchema
441
+ }),
442
+ reorderPlaylistItems: (id, body) => request({
443
+ method: "PUT",
444
+ path: `/playlists/${encodeId(id)}/tracks`,
445
+ body,
446
+ responseSchema: spotifySnapshotResponseSchema
447
+ }),
448
+ replacePlaylistItems: (id, body) => request({
449
+ method: "PUT",
450
+ path: `/playlists/${encodeId(id)}/tracks`,
451
+ body,
452
+ responseSchema: spotifySnapshotResponseSchema
453
+ }),
454
+ removeItemsFromPlaylist: (id, body) => request({
455
+ method: "DELETE",
456
+ path: `/playlists/${encodeId(id)}/tracks`,
457
+ body,
458
+ responseSchema: spotifySnapshotResponseSchema
459
+ }),
460
+ listMyPlaylists: (query) => getOffsetPage("/me/playlists", query, "items", z.array(spotifyPlaylistSchema), spotifyPlaylistPageSchema),
461
+ listUserPlaylists: (userId, query) => getOffsetPage(`/users/${encodeId(userId)}/playlists`, query, "items", z.array(spotifyPlaylistSchema), spotifyPlaylistPageSchema),
462
+ followPlaylist: (id, body) => request({
463
+ method: "PUT",
464
+ path: `/playlists/${encodeId(id)}/followers`,
465
+ body,
466
+ responseSchema: spotifyMutationSuccessSchema,
467
+ mapResponse: normalizeEmptySuccess
468
+ }),
469
+ unfollowPlaylist: (id) => request({
470
+ method: "DELETE",
471
+ path: `/playlists/${encodeId(id)}/followers`,
472
+ responseSchema: spotifyMutationSuccessSchema,
473
+ mapResponse: normalizeEmptySuccess
474
+ }),
475
+ checkUsersFollowPlaylist: (id, ids) => request({
476
+ path: `/playlists/${encodeId(id)}/followers/contains`,
477
+ query: { ids },
478
+ responseSchema: spotifyBooleanListSchema
479
+ }),
480
+ uploadPlaylistCoverImage: (id, imageBase64) => request({
481
+ method: "PUT",
482
+ path: `/playlists/${encodeId(id)}/images`,
483
+ body: imageBase64,
484
+ headers: { "Content-Type": "image/jpeg" },
485
+ responseSchema: spotifyMutationSuccessSchema,
486
+ mapResponse: normalizeEmptySuccess
487
+ })
488
+ },
489
+ users: {
490
+ getMe: () => request({
491
+ path: "/me",
492
+ responseSchema: spotifyUserSchema
493
+ }),
494
+ getUser: (userId) => request({
495
+ path: `/users/${encodeId(userId)}`,
496
+ responseSchema: spotifyUserSchema
497
+ }),
498
+ getMyTopArtists: (query) => getOffsetPage("/me/top/artists", query, "items", z.array(spotifyArtistSchema), spotifyArtistPageSchema),
499
+ getMyTopTracks: (query) => getOffsetPage("/me/top/tracks", query, "items", z.array(spotifyTrackSchema), spotifyTrackPageSchema)
500
+ },
501
+ follow: {
502
+ getFollowedArtists: (query) => request({
503
+ path: "/me/following",
504
+ query: {
505
+ ...query,
506
+ type: "artist"
507
+ },
508
+ responseSchema: createSpotifyCursorPageSchema(spotifyArtistSchema),
509
+ mapResponse: (value) => {
510
+ if (!isRecord(value)) return value;
511
+ return normalizeCursorPage(value.artists, "items", z.array(spotifyArtistSchema));
512
+ }
513
+ }),
514
+ followArtistsOrUsers: (type, ids) => request({
515
+ method: "PUT",
516
+ path: "/me/following",
517
+ query: {
518
+ type,
519
+ ids
520
+ },
521
+ responseSchema: spotifyMutationSuccessSchema,
522
+ mapResponse: normalizeEmptySuccess
523
+ }),
524
+ unfollowArtistsOrUsers: (type, ids) => request({
525
+ method: "DELETE",
526
+ path: "/me/following",
527
+ query: {
528
+ type,
529
+ ids
530
+ },
531
+ responseSchema: spotifyMutationSuccessSchema,
532
+ mapResponse: normalizeEmptySuccess
533
+ }),
534
+ checkFollowingArtistsOrUsers: (type, ids) => request({
535
+ path: "/me/following/contains",
536
+ query: {
537
+ type,
538
+ ids
539
+ },
540
+ responseSchema: spotifyBooleanListSchema
541
+ })
542
+ },
543
+ player: {
544
+ getPlaybackState: (market) => request({
545
+ path: "/me/player",
546
+ query: { market },
547
+ responseSchema: nullablePlaybackStateSchema,
548
+ mapResponse: normalizeEmptyBodyToNull
549
+ }),
550
+ transferPlayback: (body) => request({
551
+ method: "PUT",
552
+ path: "/me/player",
553
+ body,
554
+ responseSchema: spotifyMutationSuccessSchema,
555
+ mapResponse: normalizeEmptySuccess
556
+ }),
557
+ getAvailableDevices: () => request({
558
+ path: "/me/player/devices",
559
+ responseSchema: z.object({ devices: z.array(spotifyDeviceSchema) })
560
+ }),
561
+ getCurrentlyPlaying: (query) => request({
562
+ path: "/me/player/currently-playing",
563
+ query,
564
+ responseSchema: nullablePlaybackStateSchema,
565
+ mapResponse: normalizeEmptyBodyToNull
566
+ }),
567
+ startOrResumePlayback: (body, deviceId) => request({
568
+ method: "PUT",
569
+ path: "/me/player/play",
570
+ query: { device_id: deviceId },
571
+ body,
572
+ responseSchema: spotifyMutationSuccessSchema,
573
+ mapResponse: normalizeEmptySuccess
574
+ }),
575
+ pausePlayback: (deviceId) => request({
576
+ method: "PUT",
577
+ path: "/me/player/pause",
578
+ query: { device_id: deviceId },
579
+ responseSchema: spotifyMutationSuccessSchema,
580
+ mapResponse: normalizeEmptySuccess
581
+ }),
582
+ skipToNext: (deviceId) => request({
583
+ method: "POST",
584
+ path: "/me/player/next",
585
+ query: { device_id: deviceId },
586
+ responseSchema: spotifyMutationSuccessSchema,
587
+ mapResponse: normalizeEmptySuccess
588
+ }),
589
+ skipToPrevious: (deviceId) => request({
590
+ method: "POST",
591
+ path: "/me/player/previous",
592
+ query: { device_id: deviceId },
593
+ responseSchema: spotifyMutationSuccessSchema,
594
+ mapResponse: normalizeEmptySuccess
595
+ }),
596
+ seekToPosition: (positionMs, deviceId) => request({
597
+ method: "PUT",
598
+ path: "/me/player/seek",
599
+ query: {
600
+ position_ms: positionMs,
601
+ device_id: deviceId
602
+ },
603
+ responseSchema: spotifyMutationSuccessSchema,
604
+ mapResponse: normalizeEmptySuccess
605
+ }),
606
+ setRepeatMode: (state, deviceId) => request({
607
+ method: "PUT",
608
+ path: "/me/player/repeat",
609
+ query: {
610
+ state,
611
+ device_id: deviceId
612
+ },
613
+ responseSchema: spotifyMutationSuccessSchema,
614
+ mapResponse: normalizeEmptySuccess
615
+ }),
616
+ setPlaybackVolume: (volumePercent, deviceId) => request({
617
+ method: "PUT",
618
+ path: "/me/player/volume",
619
+ query: {
620
+ volume_percent: volumePercent,
621
+ device_id: deviceId
622
+ },
623
+ responseSchema: spotifyMutationSuccessSchema,
624
+ mapResponse: normalizeEmptySuccess
625
+ }),
626
+ setShuffleMode: (state, deviceId) => request({
627
+ method: "PUT",
628
+ path: "/me/player/shuffle",
629
+ query: {
630
+ state,
631
+ device_id: deviceId
632
+ },
633
+ responseSchema: spotifyMutationSuccessSchema,
634
+ mapResponse: normalizeEmptySuccess
635
+ }),
636
+ getRecentlyPlayed: (query) => getCursorPage("/me/player/recently-played", query, "items", z.array(spotifyRecentlyPlayedItemSchema), spotifyRecentlyPlayedPageSchema),
637
+ addToQueue: (uri, deviceId) => request({
638
+ method: "POST",
639
+ path: "/me/player/queue",
640
+ query: {
641
+ uri,
642
+ device_id: deviceId
643
+ },
644
+ responseSchema: spotifyMutationSuccessSchema,
645
+ mapResponse: normalizeEmptySuccess
646
+ }),
647
+ getQueue: () => request({
648
+ path: "/me/player/queue",
649
+ responseSchema: spotifyQueueSchema
650
+ })
651
+ },
652
+ library: {
653
+ listSavedAlbums: (query) => getOffsetPage("/me/albums", query, "items", z.array(spotifySavedAlbumItemSchema), spotifySavedAlbumPageSchema),
654
+ saveAlbums: (ids) => request({
655
+ method: "PUT",
656
+ path: "/me/albums",
657
+ query: { ids },
658
+ responseSchema: spotifyMutationSuccessSchema,
659
+ mapResponse: normalizeEmptySuccess
660
+ }),
661
+ removeSavedAlbums: (ids) => request({
662
+ method: "DELETE",
663
+ path: "/me/albums",
664
+ query: { ids },
665
+ responseSchema: spotifyMutationSuccessSchema,
666
+ mapResponse: normalizeEmptySuccess
667
+ }),
668
+ checkSavedAlbums: (ids) => request({
669
+ path: "/me/albums/contains",
670
+ query: { ids },
671
+ responseSchema: spotifyBooleanListSchema
672
+ }),
673
+ listSavedTracks: (query) => getOffsetPage("/me/tracks", query, "items", z.array(spotifySavedTrackItemSchema), spotifySavedTrackPageSchema),
674
+ saveTracks: (ids) => request({
675
+ method: "PUT",
676
+ path: "/me/tracks",
677
+ query: { ids },
678
+ responseSchema: spotifyMutationSuccessSchema,
679
+ mapResponse: normalizeEmptySuccess
680
+ }),
681
+ removeSavedTracks: (ids) => request({
682
+ method: "DELETE",
683
+ path: "/me/tracks",
684
+ query: { ids },
685
+ responseSchema: spotifyMutationSuccessSchema,
686
+ mapResponse: normalizeEmptySuccess
687
+ }),
688
+ checkSavedTracks: (ids) => request({
689
+ path: "/me/tracks/contains",
690
+ query: { ids },
691
+ responseSchema: spotifyBooleanListSchema
692
+ }),
693
+ listSavedShows: (query) => getOffsetPage("/me/shows", query, "items", z.array(spotifySavedShowItemSchema), spotifySavedShowPageSchema),
694
+ saveShows: (ids) => request({
695
+ method: "PUT",
696
+ path: "/me/shows",
697
+ query: { ids },
698
+ responseSchema: spotifyMutationSuccessSchema,
699
+ mapResponse: normalizeEmptySuccess
700
+ }),
701
+ removeSavedShows: (ids) => request({
702
+ method: "DELETE",
703
+ path: "/me/shows",
704
+ query: { ids },
705
+ responseSchema: spotifyMutationSuccessSchema,
706
+ mapResponse: normalizeEmptySuccess
707
+ }),
708
+ checkSavedShows: (ids) => request({
709
+ path: "/me/shows/contains",
710
+ query: { ids },
711
+ responseSchema: spotifyBooleanListSchema
712
+ }),
713
+ listSavedEpisodes: (query) => getOffsetPage("/me/episodes", query, "items", z.array(spotifySavedEpisodeItemSchema), spotifySavedEpisodePageSchema),
714
+ saveEpisodes: (ids) => request({
715
+ method: "PUT",
716
+ path: "/me/episodes",
717
+ query: { ids },
718
+ responseSchema: spotifyMutationSuccessSchema,
719
+ mapResponse: normalizeEmptySuccess
720
+ }),
721
+ removeSavedEpisodes: (ids) => request({
722
+ method: "DELETE",
723
+ path: "/me/episodes",
724
+ query: { ids },
725
+ responseSchema: spotifyMutationSuccessSchema,
726
+ mapResponse: normalizeEmptySuccess
727
+ }),
728
+ checkSavedEpisodes: (ids) => request({
729
+ path: "/me/episodes/contains",
730
+ query: { ids },
731
+ responseSchema: spotifyBooleanListSchema
732
+ }),
733
+ listSavedAudiobooks: (query) => getOffsetPage("/me/audiobooks", query, "items", z.array(spotifySavedAudiobookItemSchema), spotifySavedAudiobookPageSchema),
734
+ saveAudiobooks: (ids) => request({
735
+ method: "PUT",
736
+ path: "/me/audiobooks",
737
+ query: { ids },
738
+ responseSchema: spotifyMutationSuccessSchema,
739
+ mapResponse: normalizeEmptySuccess
740
+ }),
741
+ removeSavedAudiobooks: (ids) => request({
742
+ method: "DELETE",
743
+ path: "/me/audiobooks",
744
+ query: { ids },
745
+ responseSchema: spotifyMutationSuccessSchema,
746
+ mapResponse: normalizeEmptySuccess
747
+ }),
748
+ checkSavedAudiobooks: (ids) => request({
749
+ path: "/me/audiobooks/contains",
750
+ query: { ids },
751
+ responseSchema: spotifyBooleanListSchema
752
+ })
753
+ }
754
+ };
755
+ }
756
+
757
+ //#endregion
758
+ export { createSpotifyClient };