@bzenky/spoti 0.1.0 → 0.3.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 (80) hide show
  1. package/README.md +144 -4
  2. package/dist/app.d.ts +27 -1
  3. package/dist/app.js +514 -16
  4. package/dist/app.js.map +1 -1
  5. package/dist/auth/config.d.ts +1 -1
  6. package/dist/auth/config.js +3 -3
  7. package/dist/auth/config.js.map +1 -1
  8. package/dist/auth/spotify-auth-client.d.ts +1 -0
  9. package/dist/auth/spotify-auth-client.js +4 -0
  10. package/dist/auth/spotify-auth-client.js.map +1 -1
  11. package/dist/cli.js +61 -1
  12. package/dist/cli.js.map +1 -1
  13. package/dist/services/auth.service.d.ts +1 -1
  14. package/dist/services/auth.service.js +17 -4
  15. package/dist/services/auth.service.js.map +1 -1
  16. package/dist/services/catalog.service.d.ts +9 -0
  17. package/dist/services/catalog.service.js +22 -0
  18. package/dist/services/catalog.service.js.map +1 -0
  19. package/dist/services/device.service.d.ts +20 -0
  20. package/dist/services/device.service.js +81 -0
  21. package/dist/services/device.service.js.map +1 -0
  22. package/dist/services/library.service.d.ts +13 -0
  23. package/dist/services/library.service.js +46 -0
  24. package/dist/services/library.service.js.map +1 -0
  25. package/dist/services/mappers.d.ts +10 -0
  26. package/dist/services/mappers.js +104 -0
  27. package/dist/services/mappers.js.map +1 -0
  28. package/dist/services/models.d.ts +45 -0
  29. package/dist/services/player.service.d.ts +12 -2
  30. package/dist/services/player.service.js +80 -15
  31. package/dist/services/player.service.js.map +1 -1
  32. package/dist/services/playlist.service.d.ts +9 -0
  33. package/dist/services/playlist.service.js +33 -0
  34. package/dist/services/playlist.service.js.map +1 -0
  35. package/dist/services/queue.service.d.ts +18 -0
  36. package/dist/services/queue.service.js +43 -0
  37. package/dist/services/queue.service.js.map +1 -0
  38. package/dist/services/recent.service.d.ts +7 -0
  39. package/dist/services/recent.service.js +24 -0
  40. package/dist/services/recent.service.js.map +1 -0
  41. package/dist/services/search.service.d.ts +5 -1
  42. package/dist/services/search.service.js +32 -17
  43. package/dist/services/search.service.js.map +1 -1
  44. package/dist/services/update.service.d.ts +57 -0
  45. package/dist/services/update.service.js +313 -0
  46. package/dist/services/update.service.js.map +1 -0
  47. package/dist/spotify/client.js +14 -1
  48. package/dist/spotify/client.js.map +1 -1
  49. package/dist/spotify/scopes.d.ts +2 -1
  50. package/dist/spotify/scopes.js +9 -1
  51. package/dist/spotify/scopes.js.map +1 -1
  52. package/dist/spotify/types.d.ts +112 -8
  53. package/dist/storage/config.d.ts +30 -0
  54. package/dist/storage/config.js +128 -0
  55. package/dist/storage/config.js.map +1 -0
  56. package/dist/storage/credentials.d.ts +1 -0
  57. package/dist/storage/credentials.js +1 -0
  58. package/dist/storage/credentials.js.map +1 -1
  59. package/dist/storage/index.d.ts +2 -0
  60. package/dist/storage/index.js +1 -0
  61. package/dist/storage/index.js.map +1 -1
  62. package/dist/storage/update-cache.d.ts +29 -0
  63. package/dist/storage/update-cache.js +135 -0
  64. package/dist/storage/update-cache.js.map +1 -0
  65. package/dist/ui/output.d.ts +10 -1
  66. package/dist/ui/output.js +60 -0
  67. package/dist/ui/output.js.map +1 -1
  68. package/dist/ui/prompts.d.ts +12 -1
  69. package/dist/ui/prompts.js +73 -6
  70. package/dist/ui/prompts.js.map +1 -1
  71. package/dist/ui/watch.d.ts +7 -0
  72. package/dist/ui/watch.js +44 -0
  73. package/dist/ui/watch.js.map +1 -0
  74. package/dist/update-worker.d.ts +9 -0
  75. package/dist/update-worker.js +91 -0
  76. package/dist/update-worker.js.map +1 -0
  77. package/dist/version.d.ts +2 -0
  78. package/dist/version.js +11 -0
  79. package/dist/version.js.map +1 -0
  80. package/package.json +1 -1
@@ -1,9 +1,9 @@
1
1
  import { ConfigurationError } from '../utils/errors.js';
2
2
  export const DEFAULT_REDIRECT_URI = 'http://127.0.0.1:43821/callback';
3
- export function loadAuthConfig(environment = process.env) {
4
- const clientId = environment.SPOTIFY_CLIENT_ID?.trim();
3
+ export function loadAuthConfig(environment = process.env, storedClientId = null) {
4
+ const clientId = environment.SPOTIFY_CLIENT_ID?.trim() || storedClientId?.trim();
5
5
  if (!clientId) {
6
- throw new ConfigurationError('SPOTIFY_CLIENT_ID is required. Set it to your Spotify application client ID.');
6
+ throw new ConfigurationError('A Spotify client ID is required.\n\nRun: spoti setup\nOr set SPOTIFY_CLIENT_ID in your environment.');
7
7
  }
8
8
  const redirectUri = environment.SPOTIFY_REDIRECT_URI?.trim() || DEFAULT_REDIRECT_URI;
9
9
  validateRedirectUri(redirectUri);
@@ -1 +1 @@
1
- {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/auth/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAExD,MAAM,CAAC,MAAM,oBAAoB,GAAG,iCAAiC,CAAC;AAOtE,MAAM,UAAU,cAAc,CAC5B,cAAiC,OAAO,CAAC,GAAG;IAE5C,MAAM,QAAQ,GAAG,WAAW,CAAC,iBAAiB,EAAE,IAAI,EAAE,CAAC;IACvD,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,MAAM,IAAI,kBAAkB,CAC1B,8EAA8E,CAC/E,CAAC;IACJ,CAAC;IAED,MAAM,WAAW,GAAG,WAAW,CAAC,oBAAoB,EAAE,IAAI,EAAE,IAAI,oBAAoB,CAAC;IACrF,mBAAmB,CAAC,WAAW,CAAC,CAAC;IAEjC,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,CAAC;AACnC,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,WAAmB;IACrD,IAAI,GAAQ,CAAC;IACb,IAAI,CAAC;QACH,GAAG,GAAG,IAAI,GAAG,CAAC,WAAW,CAAC,CAAC;IAC7B,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,kBAAkB,CAAC,2CAA2C,CAAC,CAAC;IAC5E,CAAC;IAED,IACE,GAAG,CAAC,QAAQ,KAAK,OAAO;QACxB,GAAG,CAAC,QAAQ,KAAK,WAAW;QAC5B,CAAC,GAAG,CAAC,IAAI;QACT,GAAG,CAAC,QAAQ;QACZ,GAAG,CAAC,QAAQ;QACZ,GAAG,CAAC,MAAM;QACV,GAAG,CAAC,IAAI,EACR,CAAC;QACD,MAAM,IAAI,kBAAkB,CAC1B,8GAA8G,CAC/G,CAAC;IACJ,CAAC;IAED,OAAO,GAAG,CAAC;AACb,CAAC"}
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/auth/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAExD,MAAM,CAAC,MAAM,oBAAoB,GAAG,iCAAiC,CAAC;AAOtE,MAAM,UAAU,cAAc,CAC5B,cAAiC,OAAO,CAAC,GAAG,EAC5C,iBAAgC,IAAI;IAEpC,MAAM,QAAQ,GAAG,WAAW,CAAC,iBAAiB,EAAE,IAAI,EAAE,IAAI,cAAc,EAAE,IAAI,EAAE,CAAC;IACjF,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,MAAM,IAAI,kBAAkB,CAC1B,qGAAqG,CACtG,CAAC;IACJ,CAAC;IAED,MAAM,WAAW,GAAG,WAAW,CAAC,oBAAoB,EAAE,IAAI,EAAE,IAAI,oBAAoB,CAAC;IACrF,mBAAmB,CAAC,WAAW,CAAC,CAAC;IAEjC,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,CAAC;AACnC,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,WAAmB;IACrD,IAAI,GAAQ,CAAC;IACb,IAAI,CAAC;QACH,GAAG,GAAG,IAAI,GAAG,CAAC,WAAW,CAAC,CAAC;IAC7B,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,kBAAkB,CAAC,2CAA2C,CAAC,CAAC;IAC5E,CAAC;IAED,IACE,GAAG,CAAC,QAAQ,KAAK,OAAO;QACxB,GAAG,CAAC,QAAQ,KAAK,WAAW;QAC5B,CAAC,GAAG,CAAC,IAAI;QACT,GAAG,CAAC,QAAQ;QACZ,GAAG,CAAC,QAAQ;QACZ,GAAG,CAAC,MAAM;QACV,GAAG,CAAC,IAAI,EACR,CAAC;QACD,MAAM,IAAI,kBAAkB,CAC1B,8GAA8G,CAC/G,CAAC;IACJ,CAAC;IAED,OAAO,GAAG,CAAC;AACb,CAAC"}
@@ -5,6 +5,7 @@ export interface TokenSet {
5
5
  accessToken: string;
6
6
  refreshToken?: string;
7
7
  expiresIn: number;
8
+ scopes?: string[];
8
9
  }
9
10
  export interface ExchangeCodeInput {
10
11
  clientId: string;
@@ -8,6 +8,7 @@ const tokenResponseSchema = z.object({
8
8
  access_token: z.string().min(1),
9
9
  refresh_token: z.string().min(1).optional(),
10
10
  expires_in: z.number().int().positive(),
11
+ scope: z.string().optional(),
11
12
  });
12
13
  const userProfileSchema = z.object({
13
14
  id: z.string().min(1),
@@ -89,6 +90,9 @@ export class SpotifyAuthClient {
89
90
  if (token.data.refresh_token !== undefined) {
90
91
  result.refreshToken = token.data.refresh_token;
91
92
  }
93
+ if (token.data.scope !== undefined) {
94
+ result.scopes = token.data.scope.split(' ').filter(Boolean);
95
+ }
92
96
  return result;
93
97
  }
94
98
  async fetchWithTimeout(url, init) {
@@ -1 +1 @@
1
- {"version":3,"file":"spotify-auth-client.js","sourceRoot":"","sources":["../../src/auth/spotify-auth-client.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"spotify-auth-client.js","sourceRoot":"","sources":["../../src/auth/spotify-auth-client.ts"],"names":[],"mappings":"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"}
package/dist/cli.js CHANGED
@@ -1,25 +1,85 @@
1
1
  #!/usr/bin/env node
2
2
  import { createProgram } from './app.js';
3
3
  import { AuthService } from './services/auth.service.js';
4
+ import { CatalogService } from './services/catalog.service.js';
5
+ import { DeviceService } from './services/device.service.js';
6
+ import { LibraryService } from './services/library.service.js';
4
7
  import { PlayerService } from './services/player.service.js';
8
+ import { PlaylistService } from './services/playlist.service.js';
9
+ import { QueueService } from './services/queue.service.js';
10
+ import { RecentService } from './services/recent.service.js';
5
11
  import { SearchService } from './services/search.service.js';
12
+ import { compareSemanticVersions, UPDATE_CHECK_INTERVAL_MS, UpdateService, } from './services/update.service.js';
13
+ import { FileConfigStore } from './storage/config.js';
14
+ import { FileUpdateCacheStore } from './storage/update-cache.js';
6
15
  import { SpotifyClient } from './spotify/client.js';
7
16
  import { consoleOutput } from './ui/output.js';
17
+ import { startUpdateWorker } from './update-worker.js';
8
18
  import { AppError, toError } from './utils/errors.js';
19
+ import { VERSION } from './version.js';
9
20
  const auth = new AuthService();
10
21
  const spotify = new SpotifyClient(auth);
22
+ const device = new DeviceService(spotify);
23
+ const updateCache = new FileUpdateCacheStore();
24
+ const update = new UpdateService(VERSION, { cache: updateCache });
11
25
  const program = createProgram({
12
26
  auth,
13
- player: new PlayerService(spotify),
27
+ catalog: new CatalogService(spotify),
28
+ device,
29
+ library: new LibraryService(spotify),
30
+ player: new PlayerService(spotify, device),
31
+ playlist: new PlaylistService(spotify),
32
+ queue: new QueueService(spotify),
33
+ recent: new RecentService(spotify),
14
34
  search: new SearchService(spotify),
35
+ update,
36
+ config: new FileConfigStore(),
15
37
  output: consoleOutput,
16
38
  });
39
+ const isUpdateCommand = process.argv[2] === 'update';
40
+ const cachedUpdate = isUpdateCommand ? null : await updateCache.read().catch(() => null);
41
+ const updateMessage = getCachedUpdateMessage(cachedUpdate, VERSION);
42
+ if (!isUpdateCommand && shouldRefreshUpdateCache(cachedUpdate, VERSION)) {
43
+ await startUpdateWorker(VERSION, updateCache.path);
44
+ }
17
45
  try {
18
46
  await program.parseAsync(process.argv);
47
+ if (updateMessage && process.stderr.isTTY)
48
+ consoleOutput.error(updateMessage);
19
49
  }
20
50
  catch (error) {
21
51
  const normalizedError = toError(error);
22
52
  consoleOutput.error(normalizedError.message);
23
53
  process.exitCode = error instanceof AppError ? error.exitCode : 1;
24
54
  }
55
+ function shouldRefreshUpdateCache(cache, currentVersion) {
56
+ if (!cache)
57
+ return true;
58
+ const age = Date.now() - cache.lastCheckedAt;
59
+ if (age < 0 || age >= UPDATE_CHECK_INTERVAL_MS)
60
+ return true;
61
+ return (cache.lastSuccessfulCheck !== undefined &&
62
+ cache.lastSuccessfulCheck.currentVersion !== currentVersion);
63
+ }
64
+ function getCachedUpdateMessage(cache, currentVersion) {
65
+ const successful = cache?.lastSuccessfulCheck;
66
+ if (!successful || successful.currentVersion !== currentVersion)
67
+ return null;
68
+ if (cache?.lastFailedCheckAt === cache?.lastCheckedAt)
69
+ return null;
70
+ const age = Date.now() - successful.checkedAt;
71
+ if (age < 0 || age >= UPDATE_CHECK_INTERVAL_MS)
72
+ return null;
73
+ try {
74
+ if (compareSemanticVersions(successful.latestVersion, currentVersion) <= 0)
75
+ return null;
76
+ }
77
+ catch {
78
+ return null;
79
+ }
80
+ return [
81
+ `Update available: ${currentVersion} → ${successful.latestVersion}`,
82
+ 'Run: spoti update',
83
+ ].join('\n');
84
+ }
25
85
  //# sourceMappingURL=cli.js.map
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AACzD,OAAO,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAC7D,OAAO,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAC7D,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAC/C,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAEtD,MAAM,IAAI,GAAG,IAAI,WAAW,EAAE,CAAC;AAC/B,MAAM,OAAO,GAAG,IAAI,aAAa,CAAC,IAAI,CAAC,CAAC;AACxC,MAAM,OAAO,GAAG,aAAa,CAAC;IAC5B,IAAI;IACJ,MAAM,EAAE,IAAI,aAAa,CAAC,OAAO,CAAC;IAClC,MAAM,EAAE,IAAI,aAAa,CAAC,OAAO,CAAC;IAClC,MAAM,EAAE,aAAa;CACtB,CAAC,CAAC;AAEH,IAAI,CAAC;IACH,MAAM,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;AACzC,CAAC;AAAC,OAAO,KAAK,EAAE,CAAC;IACf,MAAM,eAAe,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC;IACvC,aAAa,CAAC,KAAK,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC;IAC7C,OAAO,CAAC,QAAQ,GAAG,KAAK,YAAY,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;AACpE,CAAC"}
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AACzD,OAAO,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAC/D,OAAO,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAC7D,OAAO,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAC/D,OAAO,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAC7D,OAAO,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AACjE,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAC3D,OAAO,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAC7D,OAAO,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAC7D,OAAO,EACL,uBAAuB,EACvB,wBAAwB,EACxB,aAAa,GACd,MAAM,8BAA8B,CAAC;AACtC,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,EAAE,oBAAoB,EAAoB,MAAM,2BAA2B,CAAC;AACnF,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAC/C,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAEvC,MAAM,IAAI,GAAG,IAAI,WAAW,EAAE,CAAC;AAC/B,MAAM,OAAO,GAAG,IAAI,aAAa,CAAC,IAAI,CAAC,CAAC;AACxC,MAAM,MAAM,GAAG,IAAI,aAAa,CAAC,OAAO,CAAC,CAAC;AAC1C,MAAM,WAAW,GAAG,IAAI,oBAAoB,EAAE,CAAC;AAC/C,MAAM,MAAM,GAAG,IAAI,aAAa,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC,CAAC;AAClE,MAAM,OAAO,GAAG,aAAa,CAAC;IAC5B,IAAI;IACJ,OAAO,EAAE,IAAI,cAAc,CAAC,OAAO,CAAC;IACpC,MAAM;IACN,OAAO,EAAE,IAAI,cAAc,CAAC,OAAO,CAAC;IACpC,MAAM,EAAE,IAAI,aAAa,CAAC,OAAO,EAAE,MAAM,CAAC;IAC1C,QAAQ,EAAE,IAAI,eAAe,CAAC,OAAO,CAAC;IACtC,KAAK,EAAE,IAAI,YAAY,CAAC,OAAO,CAAC;IAChC,MAAM,EAAE,IAAI,aAAa,CAAC,OAAO,CAAC;IAClC,MAAM,EAAE,IAAI,aAAa,CAAC,OAAO,CAAC;IAClC,MAAM;IACN,MAAM,EAAE,IAAI,eAAe,EAAE;IAC7B,MAAM,EAAE,aAAa;CACtB,CAAC,CAAC;AAEH,MAAM,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,QAAQ,CAAC;AACrD,MAAM,YAAY,GAAG,eAAe,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,WAAW,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;AACzF,MAAM,aAAa,GAAG,sBAAsB,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC;AACpE,IAAI,CAAC,eAAe,IAAI,wBAAwB,CAAC,YAAY,EAAE,OAAO,CAAC,EAAE,CAAC;IACxE,MAAM,iBAAiB,CAAC,OAAO,EAAE,WAAW,CAAC,IAAI,CAAC,CAAC;AACrD,CAAC;AAED,IAAI,CAAC;IACH,MAAM,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IACvC,IAAI,aAAa,IAAI,OAAO,CAAC,MAAM,CAAC,KAAK;QAAE,aAAa,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC;AAChF,CAAC;AAAC,OAAO,KAAK,EAAE,CAAC;IACf,MAAM,eAAe,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC;IACvC,aAAa,CAAC,KAAK,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC;IAC7C,OAAO,CAAC,QAAQ,GAAG,KAAK,YAAY,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;AACpE,CAAC;AAED,SAAS,wBAAwB,CAAC,KAAyB,EAAE,cAAsB;IACjF,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IACxB,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,aAAa,CAAC;IAC7C,IAAI,GAAG,GAAG,CAAC,IAAI,GAAG,IAAI,wBAAwB;QAAE,OAAO,IAAI,CAAC;IAC5D,OAAO,CACL,KAAK,CAAC,mBAAmB,KAAK,SAAS;QACvC,KAAK,CAAC,mBAAmB,CAAC,cAAc,KAAK,cAAc,CAC5D,CAAC;AACJ,CAAC;AAED,SAAS,sBAAsB,CAC7B,KAAyB,EACzB,cAAsB;IAEtB,MAAM,UAAU,GAAG,KAAK,EAAE,mBAAmB,CAAC;IAC9C,IAAI,CAAC,UAAU,IAAI,UAAU,CAAC,cAAc,KAAK,cAAc;QAAE,OAAO,IAAI,CAAC;IAC7E,IAAI,KAAK,EAAE,iBAAiB,KAAK,KAAK,EAAE,aAAa;QAAE,OAAO,IAAI,CAAC;IACnE,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,UAAU,CAAC,SAAS,CAAC;IAC9C,IAAI,GAAG,GAAG,CAAC,IAAI,GAAG,IAAI,wBAAwB;QAAE,OAAO,IAAI,CAAC;IAC5D,IAAI,CAAC;QACH,IAAI,uBAAuB,CAAC,UAAU,CAAC,aAAa,EAAE,cAAc,CAAC,IAAI,CAAC;YAAE,OAAO,IAAI,CAAC;IAC1F,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;IACD,OAAO;QACL,qBAAqB,cAAc,MAAM,UAAU,CAAC,aAAa,EAAE;QACnE,mBAAmB;KACpB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC"}
@@ -7,7 +7,7 @@ type AuthorizationCodeProvider = (authorizationUrl: string, redirectUri: string,
7
7
  export interface AuthServiceDependencies {
8
8
  credentialStore?: CredentialStore;
9
9
  spotifyAuthApi?: SpotifyAuthApi;
10
- loadConfig?: () => AuthConfig;
10
+ loadConfig?: () => AuthConfig | Promise<AuthConfig>;
11
11
  requestAuthorizationCode?: AuthorizationCodeProvider;
12
12
  createPkcePair?: () => PkcePair;
13
13
  createState?: () => string;
@@ -1,9 +1,10 @@
1
- import { SPOTIFY_SCOPES } from '../spotify/scopes.js';
1
+ import { LEGACY_SPOTIFY_SCOPES, SPOTIFY_SCOPES, } from '../spotify/scopes.js';
2
2
  import { generateOAuthState, generatePkcePair, } from '../auth/pkce.js';
3
3
  import { loadAuthConfig } from '../auth/config.js';
4
4
  import { requestAuthorizationCode } from '../auth/callback.js';
5
5
  import { OAuthTokenError, SpotifyAuthClient, } from '../auth/spotify-auth-client.js';
6
6
  import { FileCredentialStore, isTokenExpired, } from '../storage/credentials.js';
7
+ import { FileConfigStore } from '../storage/config.js';
7
8
  import { AuthenticationRequiredError, ConfigurationError, } from '../utils/errors.js';
8
9
  const AUTHORIZE_ENDPOINT = 'https://accounts.spotify.com/authorize';
9
10
  export class AuthService {
@@ -18,7 +19,12 @@ export class AuthService {
18
19
  constructor(dependencies = {}) {
19
20
  this.credentialStore = dependencies.credentialStore ?? new FileCredentialStore();
20
21
  this.spotifyAuthApi = dependencies.spotifyAuthApi ?? new SpotifyAuthClient();
21
- this.configLoader = dependencies.loadConfig ?? loadAuthConfig;
22
+ this.configLoader =
23
+ dependencies.loadConfig ??
24
+ (async () => {
25
+ const config = await new FileConfigStore().read();
26
+ return loadAuthConfig(process.env, config.spotifyClientId);
27
+ });
22
28
  this.authorizationCodeProvider =
23
29
  dependencies.requestAuthorizationCode ?? requestAuthorizationCode;
24
30
  this.pkceFactory = dependencies.createPkcePair ?? generatePkcePair;
@@ -26,7 +32,7 @@ export class AuthService {
26
32
  this.clock = dependencies.now ?? Date.now;
27
33
  }
28
34
  async login() {
29
- const config = this.configLoader();
35
+ const config = await this.configLoader();
30
36
  const pkce = this.pkceFactory();
31
37
  const state = this.stateFactory();
32
38
  const authorizationUrl = createAuthorizationUrl(config, pkce.challenge, state);
@@ -44,6 +50,7 @@ export class AuthService {
44
50
  accessToken: token.accessToken,
45
51
  refreshToken: token.refreshToken,
46
52
  expiresAt: this.clock() + token.expiresIn * 1_000,
53
+ scopes: token.scopes ?? [...SPOTIFY_SCOPES],
47
54
  };
48
55
  const user = await this.spotifyAuthApi.getCurrentUser(credentials.accessToken);
49
56
  await this.credentialStore.write(credentials);
@@ -56,6 +63,11 @@ export class AuthService {
56
63
  const credentials = await this.credentialStore.read();
57
64
  if (!credentials)
58
65
  throw new AuthenticationRequiredError();
66
+ const grantedScopes = credentials.scopes ?? [...LEGACY_SPOTIFY_SCOPES];
67
+ const missingScopes = SPOTIFY_SCOPES.filter((scope) => !grantedScopes.includes(scope));
68
+ if (missingScopes.length > 0) {
69
+ throw new AuthenticationRequiredError(`spoti needs new Spotify permissions (${missingScopes.join(', ')}).\n\nRun: spoti login`);
70
+ }
59
71
  if (!isTokenExpired(credentials, this.clock()))
60
72
  return credentials.accessToken;
61
73
  return this.startTokenRefresh(credentials);
@@ -87,7 +99,7 @@ export class AuthService {
87
99
  return this.refreshPromise;
88
100
  }
89
101
  async performTokenRefresh(credentials) {
90
- const config = this.configLoader();
102
+ const config = await this.configLoader();
91
103
  let token;
92
104
  try {
93
105
  token = await this.spotifyAuthApi.refreshAccessToken({
@@ -106,6 +118,7 @@ export class AuthService {
106
118
  accessToken: token.accessToken,
107
119
  refreshToken: token.refreshToken ?? credentials.refreshToken,
108
120
  expiresAt: this.clock() + token.expiresIn * 1_000,
121
+ scopes: token.scopes ?? credentials.scopes ?? [...LEGACY_SPOTIFY_SCOPES],
109
122
  };
110
123
  await this.credentialStore.write(updatedCredentials);
111
124
  return updatedCredentials.accessToken;
@@ -1 +1 @@
1
- {"version":3,"file":"auth.service.js","sourceRoot":"","sources":["../../src/services/auth.service.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"auth.service.js","sourceRoot":"","sources":["../../src/services/auth.service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ import type { SpotifyApi } from '../spotify/client.js';
2
+ import type { Album, AlbumDetail, Artist } from './models.js';
3
+ export declare class CatalogService {
4
+ private readonly spotify;
5
+ constructor(spotify: SpotifyApi);
6
+ getAlbum(id: string): Promise<AlbumDetail>;
7
+ getArtist(id: string): Promise<Artist>;
8
+ getArtistAlbums(id: string, limit?: number): Promise<Album[]>;
9
+ }
@@ -0,0 +1,22 @@
1
+ import { mapAlbum, mapAlbumDetail, mapArtist, normalizeLimit } from './mappers.js';
2
+ export class CatalogService {
3
+ spotify;
4
+ constructor(spotify) {
5
+ this.spotify = spotify;
6
+ }
7
+ async getAlbum(id) {
8
+ const album = await this.spotify.get(`/albums/${encodeURIComponent(id)}`);
9
+ return mapAlbumDetail(album);
10
+ }
11
+ async getArtist(id) {
12
+ const artist = await this.spotify.get(`/artists/${encodeURIComponent(id)}`);
13
+ return mapArtist(artist);
14
+ }
15
+ async getArtistAlbums(id, limit = 10) {
16
+ const response = await this.spotify.get(`/artists/${encodeURIComponent(id)}/albums`, { query: { limit: normalizeLimit(limit, 10, 10) } });
17
+ return response.items
18
+ .map(mapAlbum)
19
+ .filter((album) => album !== null);
20
+ }
21
+ }
22
+ //# sourceMappingURL=catalog.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalog.service.js","sourceRoot":"","sources":["../../src/services/catalog.service.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAGnF,MAAM,OAAO,cAAc;IACI;IAA7B,YAA6B,OAAmB;QAAnB,YAAO,GAAP,OAAO,CAAY;IAAG,CAAC;IAEpD,KAAK,CAAC,QAAQ,CAAC,EAAU;QACvB,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,GAAG,CAAe,WAAW,kBAAkB,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;QACxF,OAAO,cAAc,CAAC,KAAK,CAAC,CAAC;IAC/B,CAAC;IAED,KAAK,CAAC,SAAS,CAAC,EAAU;QACxB,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,GAAG,CACnC,YAAY,kBAAkB,CAAC,EAAE,CAAC,EAAE,CACrC,CAAC;QACF,OAAO,SAAS,CAAC,MAAM,CAAC,CAAC;IAC3B,CAAC;IAED,KAAK,CAAC,eAAe,CAAC,EAAU,EAAE,KAAK,GAAG,EAAE;QAC1C,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,GAAG,CACrC,YAAY,kBAAkB,CAAC,EAAE,CAAC,SAAS,EAC3C,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,cAAc,CAAC,KAAK,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,CACpD,CAAC;QACF,OAAO,QAAQ,CAAC,KAAK;aAClB,GAAG,CAAC,QAAQ,CAAC;aACb,MAAM,CAAC,CAAC,KAAK,EAAkB,EAAE,CAAC,KAAK,KAAK,IAAI,CAAC,CAAC;IACvD,CAAC;CACF"}
@@ -0,0 +1,20 @@
1
+ import type { SpotifyApi } from '../spotify/client.js';
2
+ export interface Device {
3
+ id: string | null;
4
+ isActive: boolean;
5
+ isPrivateSession: boolean;
6
+ isRestricted: boolean;
7
+ name: string;
8
+ type: string;
9
+ volumePercent: number | null;
10
+ supportsVolume: boolean;
11
+ }
12
+ export declare class DeviceService {
13
+ private readonly spotify;
14
+ constructor(spotify: SpotifyApi);
15
+ getDevices(): Promise<Device[]>;
16
+ getActiveDevice(): Promise<Device>;
17
+ getControllableDevices(): Promise<Device[]>;
18
+ findDevice(nameOrId: string): Promise<Device>;
19
+ transferPlayback(deviceId: string, play?: boolean): Promise<void>;
20
+ }
@@ -0,0 +1,81 @@
1
+ import { AppError, NoActiveDeviceError } from '../utils/errors.js';
2
+ export class DeviceService {
3
+ spotify;
4
+ constructor(spotify) {
5
+ this.spotify = spotify;
6
+ }
7
+ async getDevices() {
8
+ const response = await this.spotify.get('/me/player/devices');
9
+ return response.devices.map(mapDevice).sort(compareDevices);
10
+ }
11
+ async getActiveDevice() {
12
+ const activeDevice = (await this.getDevices()).find((device) => device.isActive);
13
+ if (!activeDevice)
14
+ throw new NoActiveDeviceError();
15
+ return activeDevice;
16
+ }
17
+ async getControllableDevices() {
18
+ return (await this.getDevices()).filter((device) => device.id !== null && !device.isRestricted);
19
+ }
20
+ async findDevice(nameOrId) {
21
+ const query = nameOrId.trim();
22
+ const normalizedQuery = query.toLocaleLowerCase();
23
+ const availableDevices = await this.getDevices();
24
+ if (/^\d+$/.test(query)) {
25
+ const index = Number(query) - 1;
26
+ const selected = Number.isSafeInteger(index) ? availableDevices[index] : undefined;
27
+ if (!selected) {
28
+ throw new AppError(`Device number ${query} is out of range. Run: spoti devices`);
29
+ }
30
+ if (!selected.id || selected.isRestricted) {
31
+ throw new AppError(`Device ${query} ("${selected.name}") cannot be controlled through Spotify Connect.`);
32
+ }
33
+ return selected;
34
+ }
35
+ const devices = availableDevices.filter((device) => device.id !== null && !device.isRestricted);
36
+ const idMatch = devices.find((device) => device.id?.toLocaleLowerCase() === normalizedQuery);
37
+ if (idMatch)
38
+ return idMatch;
39
+ const nameMatches = devices.filter((device) => device.name.toLocaleLowerCase() === normalizedQuery);
40
+ if (nameMatches.length === 1)
41
+ return nameMatches[0];
42
+ if (nameMatches.length > 1) {
43
+ const deviceIds = nameMatches.map((device) => device.id).join(', ');
44
+ throw new AppError(`Multiple controllable Spotify devices are named "${query}". Use a device ID instead: ${deviceIds}.`);
45
+ }
46
+ throw new AppError(`No controllable Spotify device matches "${query}". Check the device name or ID and ensure Spotify is open on that device.`);
47
+ }
48
+ async transferPlayback(deviceId, play) {
49
+ const normalizedDeviceId = deviceId.trim();
50
+ if (!normalizedDeviceId) {
51
+ throw new AppError('A Spotify device ID is required to transfer playback.');
52
+ }
53
+ await this.spotify.put('/me/player', {
54
+ body: {
55
+ device_ids: [normalizedDeviceId],
56
+ ...(play === undefined ? {} : { play }),
57
+ },
58
+ });
59
+ }
60
+ }
61
+ function compareDevices(left, right) {
62
+ const nameComparison = left.name.localeCompare(right.name, undefined, {
63
+ sensitivity: 'base',
64
+ });
65
+ if (nameComparison !== 0)
66
+ return nameComparison;
67
+ return (left.id ?? '').localeCompare(right.id ?? '');
68
+ }
69
+ function mapDevice(device) {
70
+ return {
71
+ id: device.id,
72
+ isActive: device.is_active,
73
+ isPrivateSession: device.is_private_session,
74
+ isRestricted: device.is_restricted,
75
+ name: device.name,
76
+ type: device.type,
77
+ volumePercent: device.volume_percent,
78
+ supportsVolume: device.supports_volume,
79
+ };
80
+ }
81
+ //# sourceMappingURL=device.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"device.service.js","sourceRoot":"","sources":["../../src/services/device.service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ import type { SpotifyApi } from '../spotify/client.js';
2
+ import type { SavedTrack, Track } from './models.js';
3
+ export declare class LibraryService {
4
+ private readonly spotify;
5
+ constructor(spotify: SpotifyApi);
6
+ getLikedTracks(limit?: number): Promise<SavedTrack[]>;
7
+ likeTrack(uri: string): Promise<void>;
8
+ unlikeTrack(uri: string): Promise<void>;
9
+ likeCurrentTrack(): Promise<Track | null>;
10
+ unlikeCurrentTrack(): Promise<Track | null>;
11
+ private changeCurrentTrackSavedState;
12
+ private changeSavedState;
13
+ }
@@ -0,0 +1,46 @@
1
+ import { mapPlaybackItem, mapTrack, normalizeLimit } from './mappers.js';
2
+ const DEFAULT_LIBRARY_LIMIT = 20;
3
+ export class LibraryService {
4
+ spotify;
5
+ constructor(spotify) {
6
+ this.spotify = spotify;
7
+ }
8
+ async getLikedTracks(limit = DEFAULT_LIBRARY_LIMIT) {
9
+ const response = await this.spotify.get('/me/tracks', {
10
+ query: { limit: normalizeLimit(limit) },
11
+ });
12
+ return response.items.flatMap(({ added_at: addedAt, track }) => {
13
+ const mapped = mapTrack(track);
14
+ return mapped ? [{ addedAt, track: mapped }] : [];
15
+ });
16
+ }
17
+ async likeTrack(uri) {
18
+ await this.changeSavedState('put', uri);
19
+ }
20
+ async unlikeTrack(uri) {
21
+ await this.changeSavedState('delete', uri);
22
+ }
23
+ async likeCurrentTrack() {
24
+ return this.changeCurrentTrackSavedState('put');
25
+ }
26
+ async unlikeCurrentTrack() {
27
+ return this.changeCurrentTrackSavedState('delete');
28
+ }
29
+ async changeCurrentTrackSavedState(method) {
30
+ const playback = await this.spotify.get('/me/player');
31
+ const track = mapPlaybackItem(playback?.item ?? null);
32
+ if (!track)
33
+ return null;
34
+ await this.changeSavedState(method, track.uri);
35
+ return track;
36
+ }
37
+ async changeSavedState(method, uri) {
38
+ const normalizedUri = uri.trim();
39
+ if (!normalizedUri.startsWith('spotify:track:'))
40
+ return;
41
+ await this.spotify[method]('/me/library', {
42
+ query: { uris: normalizedUri },
43
+ });
44
+ }
45
+ }
46
+ //# sourceMappingURL=library.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"library.service.js","sourceRoot":"","sources":["../../src/services/library.service.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,eAAe,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAGzE,MAAM,qBAAqB,GAAG,EAAE,CAAC;AAEjC,MAAM,OAAO,cAAc;IACI;IAA7B,YAA6B,OAAmB;QAAnB,YAAO,GAAP,OAAO,CAAY;IAAG,CAAC;IAEpD,KAAK,CAAC,cAAc,CAAC,KAAK,GAAG,qBAAqB;QAChD,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,GAAG,CAAmC,YAAY,EAAE;YACtF,KAAK,EAAE,EAAE,KAAK,EAAE,cAAc,CAAC,KAAK,CAAC,EAAE;SACxC,CAAC,CAAC;QACH,OAAO,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,EAAE,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE;YAC7D,MAAM,MAAM,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;YAC/B,OAAO,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QACpD,CAAC,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,SAAS,CAAC,GAAW;QACzB,MAAM,IAAI,CAAC,gBAAgB,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;IAC1C,CAAC;IAED,KAAK,CAAC,WAAW,CAAC,GAAW;QAC3B,MAAM,IAAI,CAAC,gBAAgB,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;IAC7C,CAAC;IAED,KAAK,CAAC,gBAAgB;QACpB,OAAO,IAAI,CAAC,4BAA4B,CAAC,KAAK,CAAC,CAAC;IAClD,CAAC;IAED,KAAK,CAAC,kBAAkB;QACtB,OAAO,IAAI,CAAC,4BAA4B,CAAC,QAAQ,CAAC,CAAC;IACrD,CAAC;IAEO,KAAK,CAAC,4BAA4B,CACxC,MAAwB;QAExB,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,GAAG,CAAmC,YAAY,CAAC,CAAC;QACxF,MAAM,KAAK,GAAG,eAAe,CAAC,QAAQ,EAAE,IAAI,IAAI,IAAI,CAAC,CAAC;QACtD,IAAI,CAAC,KAAK;YAAE,OAAO,IAAI,CAAC;QACxB,MAAM,IAAI,CAAC,gBAAgB,CAAC,MAAM,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC;QAC/C,OAAO,KAAK,CAAC;IACf,CAAC;IAEO,KAAK,CAAC,gBAAgB,CAAC,MAAwB,EAAE,GAAW;QAClE,MAAM,aAAa,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC;QACjC,IAAI,CAAC,aAAa,CAAC,UAAU,CAAC,gBAAgB,CAAC;YAAE,OAAO;QACxD,MAAM,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAO,aAAa,EAAE;YAC9C,KAAK,EAAE,EAAE,IAAI,EAAE,aAAa,EAAE;SAC/B,CAAC,CAAC;IACL,CAAC;CACF"}
@@ -0,0 +1,10 @@
1
+ import type { SpotifyAlbum, SpotifyFullArtist, SpotifyPlaybackItem, SpotifyPlaylist, SpotifySimplifiedAlbum, SpotifySimplifiedPlaylist, SpotifySimplifiedTrack, SpotifyTrack } from '../spotify/types.js';
2
+ import type { Album, AlbumDetail, Artist, Playlist, Track } from './models.js';
3
+ export declare function mapTrack(track: SpotifyTrack): Track | null;
4
+ export declare function mapSimplifiedTrack(track: SpotifySimplifiedTrack, albumName: string): Track | null;
5
+ export declare function mapPlaybackItem(item: SpotifyPlaybackItem | null): Track | null;
6
+ export declare function mapAlbum(album: SpotifySimplifiedAlbum): Album | null;
7
+ export declare function mapAlbumDetail(album: SpotifyAlbum): AlbumDetail;
8
+ export declare function mapArtist(artist: SpotifyFullArtist): Artist;
9
+ export declare function mapPlaylist(playlist: SpotifySimplifiedPlaylist | SpotifyPlaylist): Playlist;
10
+ export declare function normalizeLimit(limit: number, defaultLimit?: number, maximum?: number): number;
@@ -0,0 +1,104 @@
1
+ export function mapTrack(track) {
2
+ if (!isSpotifyTrack(track) || track.is_local || track.is_playable === false)
3
+ return null;
4
+ return mapTrackFields(track, track.album.name);
5
+ }
6
+ export function mapSimplifiedTrack(track, albumName) {
7
+ if (!isSpotifyTrack(track) || track.is_local || track.is_playable === false)
8
+ return null;
9
+ return mapTrackFields(track, albumName);
10
+ }
11
+ export function mapPlaybackItem(item) {
12
+ return isFullSpotifyTrack(item) ? mapTrack(item) : null;
13
+ }
14
+ export function mapAlbum(album) {
15
+ if (!album.id || !album.uri)
16
+ return null;
17
+ const imageUrl = album.images[0]?.url;
18
+ const externalUrl = album.external_urls?.spotify;
19
+ return {
20
+ id: album.id,
21
+ uri: album.uri,
22
+ name: album.name,
23
+ artists: (album.artists ?? []).map((artist) => artist.name),
24
+ totalTracks: album.total_tracks ?? 0,
25
+ ...(album.release_date ? { releaseDate: album.release_date } : {}),
26
+ ...(imageUrl ? { imageUrl } : {}),
27
+ ...(externalUrl ? { externalUrl } : {}),
28
+ };
29
+ }
30
+ export function mapAlbumDetail(album) {
31
+ const mapped = mapAlbum(album);
32
+ const base = mapped ?? {
33
+ id: album.id,
34
+ uri: album.uri,
35
+ name: album.name,
36
+ artists: album.artists.map((artist) => artist.name),
37
+ totalTracks: album.total_tracks ?? album.tracks.total,
38
+ };
39
+ return {
40
+ ...base,
41
+ tracks: album.tracks.items
42
+ .map((track) => mapSimplifiedTrack(track, album.name))
43
+ .filter((track) => track !== null),
44
+ };
45
+ }
46
+ export function mapArtist(artist) {
47
+ const imageUrl = artist.images[0]?.url;
48
+ const externalUrl = artist.external_urls?.spotify;
49
+ return {
50
+ id: artist.id,
51
+ uri: artist.uri,
52
+ name: artist.name,
53
+ ...(imageUrl ? { imageUrl } : {}),
54
+ ...(externalUrl ? { externalUrl } : {}),
55
+ };
56
+ }
57
+ export function mapPlaylist(playlist) {
58
+ const imageUrl = playlist.images?.[0]?.url;
59
+ const externalUrl = playlist.external_urls?.spotify;
60
+ return {
61
+ id: playlist.id,
62
+ uri: playlist.uri,
63
+ name: playlist.name,
64
+ description: playlist.description ?? '',
65
+ ownerName: playlist.owner.display_name ?? playlist.owner.id,
66
+ isPublic: playlist.public,
67
+ totalTracks: playlist.items.total,
68
+ ...(imageUrl ? { imageUrl } : {}),
69
+ ...(externalUrl ? { externalUrl } : {}),
70
+ };
71
+ }
72
+ export function normalizeLimit(limit, defaultLimit = 20, maximum = 50) {
73
+ if (!Number.isFinite(limit))
74
+ return defaultLimit;
75
+ return Math.min(maximum, Math.max(1, Math.round(limit)));
76
+ }
77
+ function mapTrackFields(track, albumName) {
78
+ const externalUrl = track.external_urls?.spotify;
79
+ return {
80
+ id: track.id,
81
+ uri: track.uri,
82
+ name: track.name,
83
+ artists: track.artists.map((artist) => artist.name),
84
+ album: albumName,
85
+ durationMs: track.duration_ms,
86
+ ...(externalUrl ? { externalUrl } : {}),
87
+ };
88
+ }
89
+ function isSpotifyTrack(item) {
90
+ return (typeof item.id === 'string' &&
91
+ item.id.length > 0 &&
92
+ typeof item.uri === 'string' &&
93
+ item.uri.startsWith('spotify:track:') &&
94
+ typeof item.name === 'string' &&
95
+ typeof item.duration_ms === 'number' &&
96
+ Array.isArray(item.artists));
97
+ }
98
+ function isFullSpotifyTrack(item) {
99
+ if (!item || item.type === 'episode' || !('album' in item))
100
+ return false;
101
+ const candidate = item;
102
+ return Boolean(candidate.album && typeof candidate.album.name === 'string');
103
+ }
104
+ //# sourceMappingURL=mappers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mappers.js","sourceRoot":"","sources":["../../src/services/mappers.ts"],"names":[],"mappings":"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"}