@bzenky/spoti 0.4.0 → 0.5.0

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 (39) hide show
  1. package/README.md +19 -7
  2. package/dist/app.d.ts +6 -2
  3. package/dist/app.js +109 -51
  4. package/dist/app.js.map +1 -1
  5. package/dist/cli.js +2 -1
  6. package/dist/cli.js.map +1 -1
  7. package/dist/services/device.service.d.ts +3 -3
  8. package/dist/services/device.service.js +19 -11
  9. package/dist/services/device.service.js.map +1 -1
  10. package/dist/services/player.service.d.ts +4 -2
  11. package/dist/services/player.service.js +35 -5
  12. package/dist/services/player.service.js.map +1 -1
  13. package/dist/services/queue.service.js +11 -6
  14. package/dist/services/queue.service.js.map +1 -1
  15. package/dist/services/search.service.d.ts +4 -4
  16. package/dist/services/search.service.js +10 -9
  17. package/dist/services/search.service.js.map +1 -1
  18. package/dist/spotify/client.d.ts +2 -0
  19. package/dist/spotify/client.js +170 -46
  20. package/dist/spotify/client.js.map +1 -1
  21. package/dist/spotify/types.d.ts +2 -0
  22. package/dist/ui/completions.js +10 -10
  23. package/dist/ui/interactive-search.d.ts +4 -1
  24. package/dist/ui/interactive-search.js +218 -55
  25. package/dist/ui/interactive-search.js.map +1 -1
  26. package/dist/ui/output.d.ts +27 -11
  27. package/dist/ui/output.js +87 -45
  28. package/dist/ui/output.js.map +1 -1
  29. package/dist/ui/prompts.d.ts +11 -1
  30. package/dist/ui/prompts.js +65 -13
  31. package/dist/ui/prompts.js.map +1 -1
  32. package/dist/ui/watch.js +3 -2
  33. package/dist/ui/watch.js.map +1 -1
  34. package/dist/utils/errors.js +2 -1
  35. package/dist/utils/errors.js.map +1 -1
  36. package/dist/utils/text.d.ts +2 -0
  37. package/dist/utils/text.js +10 -0
  38. package/dist/utils/text.js.map +1 -0
  39. package/package.json +2 -1
@@ -1,6 +1,19 @@
1
1
  import { AppError, AuthenticationRequiredError, NoActiveDeviceError, PremiumRequiredError, RateLimitedError, SpotifyApiError, toError, } from '../utils/errors.js';
2
+ import { sanitizeOneLineText } from '../utils/text.js';
2
3
  const API_BASE_URL = 'https://api.spotify.com/v1';
3
4
  const MAX_RATE_LIMIT_RETRIES = 3;
5
+ const PLAYBACK_CONTROL_REQUESTS = new Set([
6
+ 'PUT /me/player',
7
+ 'PUT /me/player/play',
8
+ 'PUT /me/player/pause',
9
+ 'POST /me/player/next',
10
+ 'POST /me/player/previous',
11
+ 'PUT /me/player/seek',
12
+ 'PUT /me/player/repeat',
13
+ 'PUT /me/player/volume',
14
+ 'PUT /me/player/shuffle',
15
+ 'POST /me/player/queue',
16
+ ]);
4
17
  const sleep = (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds));
5
18
  export class SpotifyClient {
6
19
  authService;
@@ -35,83 +48,194 @@ export class SpotifyClient {
35
48
  headers.set('Content-Type', 'application/json');
36
49
  body = JSON.stringify(options.body);
37
50
  }
38
- const execute = async (token) => {
39
- headers.set('Authorization', `Bearer ${token}`);
51
+ throwIfExternallyAborted(options.signal);
52
+ let accessToken = await this.authService.getAccessToken();
53
+ let refreshedToken = false;
54
+ let rateLimitRetries = 0;
55
+ while (true) {
56
+ throwIfExternallyAborted(options.signal);
57
+ headers.set('Authorization', `Bearer ${accessToken}`);
58
+ const attempt = createAttemptSignal(options.signal);
59
+ let shouldRefreshToken = false;
60
+ let retryDelayMs;
40
61
  try {
41
- return await this.fetcher(url, {
62
+ const response = await this.fetcher(url, {
42
63
  method,
43
64
  headers,
44
- signal: AbortSignal.timeout(15_000),
65
+ signal: attempt.signal,
45
66
  ...(body === undefined ? {} : { body }),
46
67
  });
68
+ if (response.status === 401 && !refreshedToken) {
69
+ shouldRefreshToken = true;
70
+ }
71
+ else if (response.status === 429 &&
72
+ rateLimitRetries < MAX_RATE_LIMIT_RETRIES) {
73
+ const retryAfterSeconds = getRetryAfterSeconds(response);
74
+ const exponentialBackoffMs = 500 * 2 ** rateLimitRetries;
75
+ rateLimitRetries += 1;
76
+ retryDelayMs = Math.max(retryAfterSeconds * 1_000, exponentialBackoffMs);
77
+ }
78
+ else {
79
+ if (!response.ok) {
80
+ const mappedError = await mapSpotifyError(response, method, path);
81
+ throwIfAttemptAborted(attempt.signal, options.signal);
82
+ throw mappedError;
83
+ }
84
+ if (response.status === 204)
85
+ return undefined;
86
+ const text = await response.text();
87
+ throwIfAttemptAborted(attempt.signal, options.signal);
88
+ if (!text)
89
+ return undefined;
90
+ const contentType = response.headers.get('content-type')?.toLocaleLowerCase() ?? '';
91
+ if (!contentType.includes('json')) {
92
+ if (method !== 'GET')
93
+ return undefined;
94
+ throw new SpotifyApiError('Spotify returned an unexpected non-JSON response.', response.status);
95
+ }
96
+ try {
97
+ return JSON.parse(text);
98
+ }
99
+ catch {
100
+ throw new SpotifyApiError('Spotify returned malformed JSON.', response.status);
101
+ }
102
+ }
47
103
  }
48
104
  catch (error) {
49
- const normalizedError = toError(error);
50
- if (normalizedError.name === 'TimeoutError') {
51
- throw new AppError('Spotify did not respond within 15 seconds. Try again.');
52
- }
53
- throw new AppError(`Unable to reach Spotify: ${normalizedError.message}`);
105
+ throw normalizeAttemptError(error, attempt.signal, options.signal);
54
106
  }
55
- };
56
- let accessToken = await this.authService.getAccessToken();
57
- let refreshedToken = false;
58
- let rateLimitRetries = 0;
59
- let response;
60
- while (true) {
61
- response = await execute(accessToken);
62
- if (response.status === 401 && !refreshedToken) {
107
+ finally {
108
+ attempt.cleanup();
109
+ }
110
+ if (shouldRefreshToken) {
111
+ throwIfExternallyAborted(options.signal);
63
112
  accessToken = await this.authService.forceRefreshAccessToken();
64
113
  refreshedToken = true;
65
114
  continue;
66
115
  }
67
- if (response.status === 429 && rateLimitRetries < MAX_RATE_LIMIT_RETRIES) {
68
- const retryAfterMs = getRetryAfterMilliseconds(response);
69
- const exponentialBackoffMs = 500 * 2 ** rateLimitRetries;
70
- rateLimitRetries += 1;
71
- await this.sleeper(Math.max(retryAfterMs, exponentialBackoffMs));
116
+ if (retryDelayMs !== undefined) {
117
+ await this.waitForRetry(retryDelayMs, options.signal);
72
118
  continue;
73
119
  }
74
- break;
75
120
  }
76
- if (!response.ok)
77
- throw await mapSpotifyError(response);
78
- if (response.status === 204)
79
- return undefined;
80
- const text = await response.text();
81
- if (!text)
82
- return undefined;
83
- const contentType = response.headers.get('content-type')?.toLocaleLowerCase() ?? '';
84
- if (!contentType.includes('json')) {
85
- if (method !== 'GET')
86
- return undefined;
87
- throw new SpotifyApiError('Spotify returned an unexpected non-JSON response.', response.status);
121
+ }
122
+ async waitForRetry(milliseconds, signal) {
123
+ throwIfExternallyAborted(signal);
124
+ if (!signal) {
125
+ await this.sleeper(milliseconds);
126
+ return;
127
+ }
128
+ if (this.sleeper === sleep) {
129
+ await abortableSleep(milliseconds, signal);
130
+ return;
88
131
  }
132
+ let abortListener;
133
+ const aborted = new Promise((_resolve, reject) => {
134
+ abortListener = () => reject(getExternalAbortReason(signal));
135
+ signal.addEventListener('abort', abortListener, { once: true });
136
+ if (signal.aborted)
137
+ abortListener();
138
+ });
89
139
  try {
90
- return JSON.parse(text);
140
+ await Promise.race([this.sleeper(milliseconds), aborted]);
91
141
  }
92
- catch {
93
- throw new SpotifyApiError('Spotify returned malformed JSON.', response.status);
142
+ finally {
143
+ if (abortListener)
144
+ signal.removeEventListener('abort', abortListener);
94
145
  }
95
146
  }
96
147
  }
97
- function getRetryAfterMilliseconds(response) {
98
- const seconds = Number(response.headers.get('retry-after'));
99
- return Number.isFinite(seconds) && seconds >= 0 ? seconds * 1_000 : 0;
148
+ function createAttemptSignal(externalSignal) {
149
+ const controller = new AbortController();
150
+ const abortFromExternalSignal = () => {
151
+ if (externalSignal)
152
+ controller.abort(getExternalAbortReason(externalSignal));
153
+ };
154
+ externalSignal?.addEventListener('abort', abortFromExternalSignal, { once: true });
155
+ if (externalSignal?.aborted)
156
+ abortFromExternalSignal();
157
+ const timeout = setTimeout(() => controller.abort(new DOMException('The operation timed out.', 'TimeoutError')), 15_000);
158
+ timeout.unref();
159
+ let cleanedUp = false;
160
+ return {
161
+ signal: controller.signal,
162
+ cleanup: () => {
163
+ if (cleanedUp)
164
+ return;
165
+ cleanedUp = true;
166
+ clearTimeout(timeout);
167
+ externalSignal?.removeEventListener('abort', abortFromExternalSignal);
168
+ },
169
+ };
170
+ }
171
+ function normalizeAttemptError(error, attemptSignal, externalSignal) {
172
+ if (externalSignal?.aborted)
173
+ return getExternalAbortReason(externalSignal);
174
+ const normalizedError = toError(error);
175
+ if (normalizedError.name === 'TimeoutError' ||
176
+ (attemptSignal.aborted && toError(attemptSignal.reason).name === 'TimeoutError')) {
177
+ return new AppError('Spotify did not respond within 15 seconds. Try again.');
178
+ }
179
+ if (normalizedError instanceof AppError)
180
+ return normalizedError;
181
+ return new AppError(`Unable to reach Spotify: ${sanitizeOneLineText(normalizedError.message)}`);
182
+ }
183
+ function throwIfAttemptAborted(attemptSignal, externalSignal) {
184
+ throwIfExternallyAborted(externalSignal);
185
+ if (attemptSignal.aborted)
186
+ throw attemptSignal.reason;
100
187
  }
101
- async function mapSpotifyError(response) {
188
+ function throwIfExternallyAborted(signal) {
189
+ if (signal?.aborted)
190
+ throw getExternalAbortReason(signal);
191
+ }
192
+ function getExternalAbortReason(signal) {
193
+ const reason = signal.reason;
194
+ if (reason instanceof Error && reason.name === 'AbortError')
195
+ return reason;
196
+ return new DOMException('The operation was aborted.', 'AbortError');
197
+ }
198
+ function abortableSleep(milliseconds, signal) {
199
+ return new Promise((resolve, reject) => {
200
+ const timer = setTimeout(() => {
201
+ signal.removeEventListener('abort', abortListener);
202
+ resolve();
203
+ }, milliseconds);
204
+ const abortListener = () => {
205
+ clearTimeout(timer);
206
+ reject(getExternalAbortReason(signal));
207
+ };
208
+ signal.addEventListener('abort', abortListener, { once: true });
209
+ if (signal.aborted)
210
+ abortListener();
211
+ });
212
+ }
213
+ function getRetryAfterSeconds(response) {
214
+ const header = response.headers.get('retry-after');
215
+ if (header === null)
216
+ return 1;
217
+ const seconds = Number(header);
218
+ if (!Number.isFinite(seconds) || seconds < 0)
219
+ return 1;
220
+ return Math.ceil(seconds);
221
+ }
222
+ async function mapSpotifyError(response, method, path) {
102
223
  const body = await readErrorBody(response);
103
224
  const spotifyMessage = typeof body.error === 'object' ? body.error?.message : body.error_description;
104
- const message = spotifyMessage || `Spotify API request failed with HTTP ${response.status}.`;
225
+ const sanitizedMessage = spotifyMessage ? sanitizeOneLineText(spotifyMessage) : '';
226
+ const message = sanitizedMessage || `Spotify API request failed with HTTP ${response.status}.`;
105
227
  if (response.status === 401) {
106
228
  return new AuthenticationRequiredError('Spotify authentication has expired or was revoked.\n\nRun: spoti login');
107
229
  }
108
230
  if (response.status === 403 && /premium/i.test(message))
109
231
  return new PremiumRequiredError();
110
- if (response.status === 404 && /device/i.test(message))
232
+ if (response.status === 404 &&
233
+ PLAYBACK_CONTROL_REQUESTS.has(`${method} ${path}`) &&
234
+ /\b(?:no active device|device not found)\b/i.test(message)) {
111
235
  return new NoActiveDeviceError();
236
+ }
112
237
  if (response.status === 429) {
113
- const retryAfter = Number(response.headers.get('retry-after') ?? '1');
114
- return new RateLimitedError(Number.isFinite(retryAfter) ? retryAfter : 1);
238
+ return new RateLimitedError(getRetryAfterSeconds(response));
115
239
  }
116
240
  return new SpotifyApiError(message, response.status);
117
241
  }
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/spotify/client.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/spotify/client.ts"],"names":[],"mappings":"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"}
@@ -122,6 +122,8 @@ export interface SpotifyCursorPaging<T> {
122
122
  }
123
123
  export interface SpotifyPlaybackState {
124
124
  is_playing: boolean;
125
+ repeat_state: string;
126
+ shuffle_state: boolean;
125
127
  progress_ms: number | null;
126
128
  item: SpotifyPlaybackItem | null;
127
129
  device: {
@@ -77,16 +77,16 @@ _spoti() {
77
77
  'seek:seek within the current track'
78
78
  'volume:set or adjust the active device volume'
79
79
  'queue:show the playback queue or add a searched track'
80
- 'shuffle:turn playback shuffle on or off'
81
- 'repeat:set the playback repeat mode'
80
+ 'shuffle:show or change the playback shuffle state'
81
+ 'repeat:show or change the playback repeat mode'
82
82
  'album:search for and show an album'
83
83
  'artist:search for and show an artist'
84
- 'playlists:list your Spotify playlists'
84
+ 'playlists:list and optionally play your Spotify playlists'
85
85
  'playlist:show one of your Spotify playlists'
86
- 'liked:list your liked tracks'
86
+ 'liked:list and optionally play your liked tracks'
87
87
  'like:add the current track to your Spotify library'
88
88
  'unlike:remove the current track from your Spotify library'
89
- 'recent:show recently played tracks'
89
+ 'recent:show and optionally play recently played tracks'
90
90
  'update:check for or install the latest spoti version'
91
91
  'search:search Spotify tracks'
92
92
  'play:play a track, album, artist, or playlist'
@@ -191,16 +191,16 @@ complete -c spoti -n __spoti_needs_command -a device -d 'Transfer playback to a
191
191
  complete -c spoti -n __spoti_needs_command -a seek -d 'Seek within the current track'
192
192
  complete -c spoti -n __spoti_needs_command -a volume -d 'Set or adjust volume'
193
193
  complete -c spoti -n __spoti_needs_command -a queue -d 'Show or add to the queue'
194
- complete -c spoti -n __spoti_needs_command -a shuffle -d 'Turn shuffle on or off'
195
- complete -c spoti -n __spoti_needs_command -a repeat -d 'Set repeat mode'
194
+ complete -c spoti -n __spoti_needs_command -a shuffle -d 'Show or change playback shuffle state'
195
+ complete -c spoti -n __spoti_needs_command -a repeat -d 'Show or change playback repeat mode'
196
196
  complete -c spoti -n __spoti_needs_command -a album -d 'Search for an album'
197
197
  complete -c spoti -n __spoti_needs_command -a artist -d 'Search for an artist'
198
- complete -c spoti -n __spoti_needs_command -a playlists -d 'List your playlists'
198
+ complete -c spoti -n __spoti_needs_command -a playlists -d 'List and optionally play your playlists'
199
199
  complete -c spoti -n __spoti_needs_command -a playlist -d 'Show a playlist'
200
- complete -c spoti -n __spoti_needs_command -a liked -d 'List liked tracks'
200
+ complete -c spoti -n __spoti_needs_command -a liked -d 'List and optionally play liked tracks'
201
201
  complete -c spoti -n __spoti_needs_command -a like -d 'Like the current track'
202
202
  complete -c spoti -n __spoti_needs_command -a unlike -d 'Unlike the current track'
203
- complete -c spoti -n __spoti_needs_command -a recent -d 'Show recently played tracks'
203
+ complete -c spoti -n __spoti_needs_command -a recent -d 'Show and optionally play recently played tracks'
204
204
  complete -c spoti -n __spoti_needs_command -a update -d 'Check for or install updates'
205
205
  complete -c spoti -n __spoti_needs_command -a search -d 'Search Spotify tracks'
206
206
  complete -c spoti -n __spoti_needs_command -a play -d 'Play a track, album, artist, or playlist'
@@ -1,5 +1,6 @@
1
1
  import type { PlayerService } from '../services/player.service.js';
2
2
  import type { SearchService } from '../services/search.service.js';
3
+ import { type OutputStyles } from './output.js';
3
4
  export type SearchCategory = 'track' | 'album' | 'artist' | 'playlist';
4
5
  export type InteractiveSearchKey = {
5
6
  type: 'character';
@@ -11,7 +12,7 @@ export interface InteractiveSearchTerminal {
11
12
  readonly isTTY: boolean;
12
13
  setup(): void;
13
14
  cleanup(): void;
14
- readKey(): Promise<InteractiveSearchKey>;
15
+ readKey(signal?: AbortSignal): Promise<InteractiveSearchKey>;
15
16
  write(value: string): void;
16
17
  }
17
18
  export interface InteractiveSearchServices {
@@ -21,6 +22,7 @@ export interface InteractiveSearchServices {
21
22
  export interface InteractiveSearchOptions extends InteractiveSearchServices {
22
23
  terminal?: InteractiveSearchTerminal;
23
24
  limit?: number;
25
+ styles?: OutputStyles;
24
26
  }
25
27
  export type InteractiveSearchResult = {
26
28
  status: 'not-interactive' | 'cancelled';
@@ -28,6 +30,7 @@ export type InteractiveSearchResult = {
28
30
  status: 'played';
29
31
  category: SearchCategory;
30
32
  uri: string;
33
+ label: string;
31
34
  };
32
35
  export declare function runInteractiveSearch(options: InteractiveSearchOptions): Promise<InteractiveSearchResult>;
33
36
  export declare function createNodeInteractiveSearchTerminal(): InteractiveSearchTerminal;