@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.
- package/README.md +144 -4
- package/dist/app.d.ts +27 -1
- package/dist/app.js +514 -16
- package/dist/app.js.map +1 -1
- package/dist/auth/config.d.ts +1 -1
- package/dist/auth/config.js +3 -3
- package/dist/auth/config.js.map +1 -1
- package/dist/auth/spotify-auth-client.d.ts +1 -0
- package/dist/auth/spotify-auth-client.js +4 -0
- package/dist/auth/spotify-auth-client.js.map +1 -1
- package/dist/cli.js +61 -1
- package/dist/cli.js.map +1 -1
- package/dist/services/auth.service.d.ts +1 -1
- package/dist/services/auth.service.js +17 -4
- package/dist/services/auth.service.js.map +1 -1
- package/dist/services/catalog.service.d.ts +9 -0
- package/dist/services/catalog.service.js +22 -0
- package/dist/services/catalog.service.js.map +1 -0
- package/dist/services/device.service.d.ts +20 -0
- package/dist/services/device.service.js +81 -0
- package/dist/services/device.service.js.map +1 -0
- package/dist/services/library.service.d.ts +13 -0
- package/dist/services/library.service.js +46 -0
- package/dist/services/library.service.js.map +1 -0
- package/dist/services/mappers.d.ts +10 -0
- package/dist/services/mappers.js +104 -0
- package/dist/services/mappers.js.map +1 -0
- package/dist/services/models.d.ts +45 -0
- package/dist/services/player.service.d.ts +12 -2
- package/dist/services/player.service.js +80 -15
- package/dist/services/player.service.js.map +1 -1
- package/dist/services/playlist.service.d.ts +9 -0
- package/dist/services/playlist.service.js +33 -0
- package/dist/services/playlist.service.js.map +1 -0
- package/dist/services/queue.service.d.ts +18 -0
- package/dist/services/queue.service.js +43 -0
- package/dist/services/queue.service.js.map +1 -0
- package/dist/services/recent.service.d.ts +7 -0
- package/dist/services/recent.service.js +24 -0
- package/dist/services/recent.service.js.map +1 -0
- package/dist/services/search.service.d.ts +5 -1
- package/dist/services/search.service.js +32 -17
- package/dist/services/search.service.js.map +1 -1
- package/dist/services/update.service.d.ts +57 -0
- package/dist/services/update.service.js +313 -0
- package/dist/services/update.service.js.map +1 -0
- package/dist/spotify/client.js +14 -1
- package/dist/spotify/client.js.map +1 -1
- package/dist/spotify/scopes.d.ts +2 -1
- package/dist/spotify/scopes.js +9 -1
- package/dist/spotify/scopes.js.map +1 -1
- package/dist/spotify/types.d.ts +112 -8
- package/dist/storage/config.d.ts +30 -0
- package/dist/storage/config.js +128 -0
- package/dist/storage/config.js.map +1 -0
- package/dist/storage/credentials.d.ts +1 -0
- package/dist/storage/credentials.js +1 -0
- package/dist/storage/credentials.js.map +1 -1
- package/dist/storage/index.d.ts +2 -0
- package/dist/storage/index.js +1 -0
- package/dist/storage/index.js.map +1 -1
- package/dist/storage/update-cache.d.ts +29 -0
- package/dist/storage/update-cache.js +135 -0
- package/dist/storage/update-cache.js.map +1 -0
- package/dist/ui/output.d.ts +10 -1
- package/dist/ui/output.js +60 -0
- package/dist/ui/output.js.map +1 -1
- package/dist/ui/prompts.d.ts +12 -1
- package/dist/ui/prompts.js +73 -6
- package/dist/ui/prompts.js.map +1 -1
- package/dist/ui/watch.d.ts +7 -0
- package/dist/ui/watch.js +44 -0
- package/dist/ui/watch.js.map +1 -0
- package/dist/update-worker.d.ts +9 -0
- package/dist/update-worker.js +91 -0
- package/dist/update-worker.js.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.js +11 -0
- package/dist/version.js.map +1 -0
- package/package.json +1 -1
|
@@ -18,3 +18,48 @@ export interface CurrentPlayback {
|
|
|
18
18
|
progressMs: number;
|
|
19
19
|
deviceName?: string;
|
|
20
20
|
}
|
|
21
|
+
export interface Album {
|
|
22
|
+
id: string;
|
|
23
|
+
uri: string;
|
|
24
|
+
name: string;
|
|
25
|
+
artists: string[];
|
|
26
|
+
releaseDate?: string;
|
|
27
|
+
totalTracks: number;
|
|
28
|
+
imageUrl?: string;
|
|
29
|
+
externalUrl?: string;
|
|
30
|
+
}
|
|
31
|
+
export interface AlbumDetail extends Album {
|
|
32
|
+
tracks: Track[];
|
|
33
|
+
}
|
|
34
|
+
export interface Artist {
|
|
35
|
+
id: string;
|
|
36
|
+
uri: string;
|
|
37
|
+
name: string;
|
|
38
|
+
imageUrl?: string;
|
|
39
|
+
externalUrl?: string;
|
|
40
|
+
}
|
|
41
|
+
export interface Playlist {
|
|
42
|
+
id: string;
|
|
43
|
+
uri: string;
|
|
44
|
+
name: string;
|
|
45
|
+
description: string;
|
|
46
|
+
ownerName: string;
|
|
47
|
+
isPublic: boolean | null;
|
|
48
|
+
totalTracks: number;
|
|
49
|
+
imageUrl?: string;
|
|
50
|
+
externalUrl?: string;
|
|
51
|
+
}
|
|
52
|
+
export interface PlaylistDetail extends Playlist {
|
|
53
|
+
tracks: Track[];
|
|
54
|
+
}
|
|
55
|
+
export interface SavedTrack {
|
|
56
|
+
addedAt: string;
|
|
57
|
+
track: Track;
|
|
58
|
+
}
|
|
59
|
+
export interface RecentlyPlayedTrack {
|
|
60
|
+
playedAt: string;
|
|
61
|
+
track: Track;
|
|
62
|
+
contextUri?: string;
|
|
63
|
+
}
|
|
64
|
+
export type RepeatMode = 'off' | 'track' | 'context';
|
|
65
|
+
export type SpotifyContextType = 'album' | 'artist' | 'playlist';
|
|
@@ -1,12 +1,22 @@
|
|
|
1
1
|
import type { SpotifyApi } from '../spotify/client.js';
|
|
2
|
-
import type {
|
|
2
|
+
import type { DeviceService } from './device.service.js';
|
|
3
|
+
import type { CurrentPlayback, RepeatMode } from './models.js';
|
|
3
4
|
export declare class PlayerService {
|
|
4
5
|
private readonly spotify;
|
|
5
|
-
|
|
6
|
+
private readonly deviceService?;
|
|
7
|
+
constructor(spotify: SpotifyApi, deviceService?: Pick<DeviceService, "getControllableDevices"> | undefined);
|
|
6
8
|
getCurrentPlayback(): Promise<CurrentPlayback | null>;
|
|
7
9
|
playTrack(uri: string): Promise<void>;
|
|
10
|
+
playContext(contextUri: string): Promise<void>;
|
|
11
|
+
setShuffle(state: boolean): Promise<void>;
|
|
12
|
+
setRepeat(state: RepeatMode): Promise<void>;
|
|
8
13
|
pause(): Promise<void>;
|
|
9
14
|
resume(): Promise<void>;
|
|
10
15
|
next(): Promise<void>;
|
|
11
16
|
previous(): Promise<void>;
|
|
17
|
+
setVolume(volumePercent: number): Promise<number>;
|
|
18
|
+
changeVolume(delta: number): Promise<number>;
|
|
19
|
+
seek(positionMs: number): Promise<number>;
|
|
20
|
+
changePosition(deltaMs: number): Promise<number>;
|
|
21
|
+
private putWithDeviceFallback;
|
|
12
22
|
}
|
|
@@ -1,27 +1,49 @@
|
|
|
1
|
+
import { AppError, NoActiveDeviceError } from '../utils/errors.js';
|
|
2
|
+
import { mapPlaybackItem } from './mappers.js';
|
|
1
3
|
export class PlayerService {
|
|
2
4
|
spotify;
|
|
3
|
-
|
|
5
|
+
deviceService;
|
|
6
|
+
constructor(spotify, deviceService) {
|
|
4
7
|
this.spotify = spotify;
|
|
8
|
+
this.deviceService = deviceService;
|
|
5
9
|
}
|
|
6
10
|
async getCurrentPlayback() {
|
|
7
11
|
const playback = await this.spotify.get('/me/player');
|
|
8
12
|
if (!playback?.item)
|
|
9
13
|
return null;
|
|
14
|
+
const track = mapPlaybackItem(playback.item);
|
|
15
|
+
if (!track)
|
|
16
|
+
return null;
|
|
10
17
|
return {
|
|
11
18
|
isPlaying: playback.is_playing,
|
|
12
|
-
track
|
|
19
|
+
track,
|
|
13
20
|
progressMs: playback.progress_ms ?? 0,
|
|
14
21
|
deviceName: playback.device.name,
|
|
15
22
|
};
|
|
16
23
|
}
|
|
17
24
|
async playTrack(uri) {
|
|
18
|
-
await this.
|
|
25
|
+
await this.putWithDeviceFallback('/me/player/play', { body: { uris: [uri] } });
|
|
26
|
+
}
|
|
27
|
+
async playContext(contextUri) {
|
|
28
|
+
await this.putWithDeviceFallback('/me/player/play', {
|
|
29
|
+
body: { context_uri: contextUri },
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
async setShuffle(state) {
|
|
33
|
+
await this.putWithDeviceFallback('/me/player/shuffle', {
|
|
34
|
+
query: { state },
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
async setRepeat(state) {
|
|
38
|
+
await this.putWithDeviceFallback('/me/player/repeat', {
|
|
39
|
+
query: { state },
|
|
40
|
+
});
|
|
19
41
|
}
|
|
20
42
|
async pause() {
|
|
21
43
|
await this.spotify.put('/me/player/pause');
|
|
22
44
|
}
|
|
23
45
|
async resume() {
|
|
24
|
-
await this.
|
|
46
|
+
await this.putWithDeviceFallback('/me/player/play');
|
|
25
47
|
}
|
|
26
48
|
async next() {
|
|
27
49
|
await this.spotify.post('/me/player/next');
|
|
@@ -29,17 +51,60 @@ export class PlayerService {
|
|
|
29
51
|
async previous() {
|
|
30
52
|
await this.spotify.post('/me/player/previous');
|
|
31
53
|
}
|
|
54
|
+
async setVolume(volumePercent) {
|
|
55
|
+
const volume = clampVolume(volumePercent);
|
|
56
|
+
await this.spotify.put('/me/player/volume', {
|
|
57
|
+
query: { volume_percent: volume },
|
|
58
|
+
});
|
|
59
|
+
return volume;
|
|
60
|
+
}
|
|
61
|
+
async changeVolume(delta) {
|
|
62
|
+
const playback = await this.spotify.get('/me/player');
|
|
63
|
+
if (!playback)
|
|
64
|
+
throw new NoActiveDeviceError();
|
|
65
|
+
if (!playback.device.supports_volume || playback.device.volume_percent === null) {
|
|
66
|
+
throw new AppError('The active Spotify device does not support volume control.');
|
|
67
|
+
}
|
|
68
|
+
return this.setVolume(playback.device.volume_percent + delta);
|
|
69
|
+
}
|
|
70
|
+
async seek(positionMs) {
|
|
71
|
+
const position = Math.max(0, Math.round(positionMs));
|
|
72
|
+
await this.spotify.put('/me/player/seek', {
|
|
73
|
+
query: { position_ms: position },
|
|
74
|
+
});
|
|
75
|
+
return position;
|
|
76
|
+
}
|
|
77
|
+
async changePosition(deltaMs) {
|
|
78
|
+
const playback = await this.spotify.get('/me/player');
|
|
79
|
+
if (!playback?.item) {
|
|
80
|
+
throw new AppError('Nothing is currently playing, so there is no position to seek.');
|
|
81
|
+
}
|
|
82
|
+
return this.seek((playback.progress_ms ?? 0) + deltaMs);
|
|
83
|
+
}
|
|
84
|
+
async putWithDeviceFallback(path, options) {
|
|
85
|
+
try {
|
|
86
|
+
await this.spotify.put(path, options);
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
if (!(error instanceof NoActiveDeviceError) || !this.deviceService)
|
|
91
|
+
throw error;
|
|
92
|
+
}
|
|
93
|
+
const devices = await this.deviceService.getControllableDevices();
|
|
94
|
+
const device = devices.length === 1 ? devices[0] : undefined;
|
|
95
|
+
if (!device?.id) {
|
|
96
|
+
if (devices.length > 1) {
|
|
97
|
+
throw new AppError('Multiple Spotify devices are available, but none is active.\n\nRun: spoti devices\nThen select one with: spoti device <number>');
|
|
98
|
+
}
|
|
99
|
+
throw new NoActiveDeviceError();
|
|
100
|
+
}
|
|
101
|
+
await this.spotify.put(path, {
|
|
102
|
+
...options,
|
|
103
|
+
query: { ...options?.query, device_id: device.id },
|
|
104
|
+
});
|
|
105
|
+
}
|
|
32
106
|
}
|
|
33
|
-
function
|
|
34
|
-
|
|
35
|
-
return {
|
|
36
|
-
id: track.id,
|
|
37
|
-
uri: track.uri,
|
|
38
|
-
name: track.name,
|
|
39
|
-
artists: track.artists.map((artist) => artist.name),
|
|
40
|
-
album: track.album.name,
|
|
41
|
-
durationMs: track.duration_ms,
|
|
42
|
-
...(externalUrl ? { externalUrl } : {}),
|
|
43
|
-
};
|
|
107
|
+
function clampVolume(volume) {
|
|
108
|
+
return Math.min(100, Math.max(0, Math.round(volume)));
|
|
44
109
|
}
|
|
45
110
|
//# sourceMappingURL=player.service.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"player.service.js","sourceRoot":"","sources":["../../src/services/player.service.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"player.service.js","sourceRoot":"","sources":["../../src/services/player.service.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,QAAQ,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAEnE,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAG/C,MAAM,OAAO,aAAa;IAEL;IACA;IAFnB,YACmB,OAAmB,EACnB,aAA6D;QAD7D,YAAO,GAAP,OAAO,CAAY;QACnB,kBAAa,GAAb,aAAa,CAAgD;IAC7E,CAAC;IAEJ,KAAK,CAAC,kBAAkB;QACtB,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,GAAG,CAAmC,YAAY,CAAC,CAAC;QACxF,IAAI,CAAC,QAAQ,EAAE,IAAI;YAAE,OAAO,IAAI,CAAC;QACjC,MAAM,KAAK,GAAG,eAAe,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;QAC7C,IAAI,CAAC,KAAK;YAAE,OAAO,IAAI,CAAC;QACxB,OAAO;YACL,SAAS,EAAE,QAAQ,CAAC,UAAU;YAC9B,KAAK;YACL,UAAU,EAAE,QAAQ,CAAC,WAAW,IAAI,CAAC;YACrC,UAAU,EAAE,QAAQ,CAAC,MAAM,CAAC,IAAI;SACjC,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,SAAS,CAAC,GAAW;QACzB,MAAM,IAAI,CAAC,qBAAqB,CAAC,iBAAiB,EAAE,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC;IACjF,CAAC;IAED,KAAK,CAAC,WAAW,CAAC,UAAkB;QAClC,MAAM,IAAI,CAAC,qBAAqB,CAAC,iBAAiB,EAAE;YAClD,IAAI,EAAE,EAAE,WAAW,EAAE,UAAU,EAAE;SAClC,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,UAAU,CAAC,KAAc;QAC7B,MAAM,IAAI,CAAC,qBAAqB,CAAC,oBAAoB,EAAE;YACrD,KAAK,EAAE,EAAE,KAAK,EAAE;SACjB,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,SAAS,CAAC,KAAiB;QAC/B,MAAM,IAAI,CAAC,qBAAqB,CAAC,mBAAmB,EAAE;YACpD,KAAK,EAAE,EAAE,KAAK,EAAE;SACjB,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,KAAK;QACT,MAAM,IAAI,CAAC,OAAO,CAAC,GAAG,CAAO,kBAAkB,CAAC,CAAC;IACnD,CAAC;IAED,KAAK,CAAC,MAAM;QACV,MAAM,IAAI,CAAC,qBAAqB,CAAC,iBAAiB,CAAC,CAAC;IACtD,CAAC;IAED,KAAK,CAAC,IAAI;QACR,MAAM,IAAI,CAAC,OAAO,CAAC,IAAI,CAAO,iBAAiB,CAAC,CAAC;IACnD,CAAC;IAED,KAAK,CAAC,QAAQ;QACZ,MAAM,IAAI,CAAC,OAAO,CAAC,IAAI,CAAO,qBAAqB,CAAC,CAAC;IACvD,CAAC;IAED,KAAK,CAAC,SAAS,CAAC,aAAqB;QACnC,MAAM,MAAM,GAAG,WAAW,CAAC,aAAa,CAAC,CAAC;QAC1C,MAAM,IAAI,CAAC,OAAO,CAAC,GAAG,CAAO,mBAAmB,EAAE;YAChD,KAAK,EAAE,EAAE,cAAc,EAAE,MAAM,EAAE;SAClC,CAAC,CAAC;QACH,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,KAAK,CAAC,YAAY,CAAC,KAAa;QAC9B,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,GAAG,CAAmC,YAAY,CAAC,CAAC;QACxF,IAAI,CAAC,QAAQ;YAAE,MAAM,IAAI,mBAAmB,EAAE,CAAC;QAC/C,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,eAAe,IAAI,QAAQ,CAAC,MAAM,CAAC,cAAc,KAAK,IAAI,EAAE,CAAC;YAChF,MAAM,IAAI,QAAQ,CAAC,4DAA4D,CAAC,CAAC;QACnF,CAAC;QACD,OAAO,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,MAAM,CAAC,cAAc,GAAG,KAAK,CAAC,CAAC;IAChE,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,UAAkB;QAC3B,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC;QACrD,MAAM,IAAI,CAAC,OAAO,CAAC,GAAG,CAAO,iBAAiB,EAAE;YAC9C,KAAK,EAAE,EAAE,WAAW,EAAE,QAAQ,EAAE;SACjC,CAAC,CAAC;QACH,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED,KAAK,CAAC,cAAc,CAAC,OAAe;QAClC,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,GAAG,CAAmC,YAAY,CAAC,CAAC;QACxF,IAAI,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC;YACpB,MAAM,IAAI,QAAQ,CAAC,gEAAgE,CAAC,CAAC;QACvF,CAAC;QACD,OAAO,IAAI,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,WAAW,IAAI,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC;IAC1D,CAAC;IAEO,KAAK,CAAC,qBAAqB,CACjC,IAAY,EACZ,OAAwB;QAExB,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,OAAO,CAAC,GAAG,CAAO,IAAI,EAAE,OAAO,CAAC,CAAC;YAC5C,OAAO;QACT,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,CAAC,KAAK,YAAY,mBAAmB,CAAC,IAAI,CAAC,IAAI,CAAC,aAAa;gBAAE,MAAM,KAAK,CAAC;QAClF,CAAC;QAED,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,aAAa,CAAC,sBAAsB,EAAE,CAAC;QAClE,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QAC7D,IAAI,CAAC,MAAM,EAAE,EAAE,EAAE,CAAC;YAChB,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACvB,MAAM,IAAI,QAAQ,CAChB,gIAAgI,CACjI,CAAC;YACJ,CAAC;YACD,MAAM,IAAI,mBAAmB,EAAE,CAAC;QAClC,CAAC;QAED,MAAM,IAAI,CAAC,OAAO,CAAC,GAAG,CAAO,IAAI,EAAE;YACjC,GAAG,OAAO;YACV,KAAK,EAAE,EAAE,GAAG,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,CAAC,EAAE,EAAE;SACnD,CAAC,CAAC;IACL,CAAC;CACF;AAED,SAAS,WAAW,CAAC,MAAc;IACjC,OAAO,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;AACxD,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { SpotifyApi } from '../spotify/client.js';
|
|
2
|
+
import type { Playlist, PlaylistDetail, Track } from './models.js';
|
|
3
|
+
export declare class PlaylistService {
|
|
4
|
+
private readonly spotify;
|
|
5
|
+
constructor(spotify: SpotifyApi);
|
|
6
|
+
listPlaylists(limit?: number): Promise<Playlist[]>;
|
|
7
|
+
getPlaylist(id: string, itemLimit?: number): Promise<PlaylistDetail>;
|
|
8
|
+
getPlaylistItems(id: string, limit?: number): Promise<Track[]>;
|
|
9
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { mapPlaybackItem, mapPlaylist, normalizeLimit } from './mappers.js';
|
|
2
|
+
const DEFAULT_PLAYLIST_LIMIT = 20;
|
|
3
|
+
const DEFAULT_ITEM_LIMIT = 50;
|
|
4
|
+
export class PlaylistService {
|
|
5
|
+
spotify;
|
|
6
|
+
constructor(spotify) {
|
|
7
|
+
this.spotify = spotify;
|
|
8
|
+
}
|
|
9
|
+
async listPlaylists(limit = DEFAULT_PLAYLIST_LIMIT) {
|
|
10
|
+
const response = await this.spotify.get('/me/playlists', { query: { limit: normalizeLimit(limit) } });
|
|
11
|
+
return response.items.map(mapPlaylist).sort(comparePlaylists);
|
|
12
|
+
}
|
|
13
|
+
async getPlaylist(id, itemLimit = DEFAULT_ITEM_LIMIT) {
|
|
14
|
+
const playlist = await this.spotify.get(`/playlists/${encodeURIComponent(id)}`);
|
|
15
|
+
const tracks = await this.getPlaylistItems(id, itemLimit);
|
|
16
|
+
return { ...mapPlaylist(playlist), tracks };
|
|
17
|
+
}
|
|
18
|
+
async getPlaylistItems(id, limit = DEFAULT_ITEM_LIMIT) {
|
|
19
|
+
const response = await this.spotify.get(`/playlists/${encodeURIComponent(id)}/items`, { query: { limit: normalizeLimit(limit) } });
|
|
20
|
+
return response.items
|
|
21
|
+
.map(({ item }) => mapPlaybackItem(item))
|
|
22
|
+
.filter((track) => track !== null);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function comparePlaylists(left, right) {
|
|
26
|
+
const nameComparison = left.name.localeCompare(right.name, undefined, {
|
|
27
|
+
sensitivity: 'base',
|
|
28
|
+
});
|
|
29
|
+
if (nameComparison !== 0)
|
|
30
|
+
return nameComparison;
|
|
31
|
+
return left.id.localeCompare(right.id);
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=playlist.service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"playlist.service.js","sourceRoot":"","sources":["../../src/services/playlist.service.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAG5E,MAAM,sBAAsB,GAAG,EAAE,CAAC;AAClC,MAAM,kBAAkB,GAAG,EAAE,CAAC;AAE9B,MAAM,OAAO,eAAe;IACG;IAA7B,YAA6B,OAAmB;QAAnB,YAAO,GAAP,OAAO,CAAY;IAAG,CAAC;IAEpD,KAAK,CAAC,aAAa,CAAC,KAAK,GAAG,sBAAsB;QAChD,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,GAAG,CACrC,eAAe,EACf,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,cAAc,CAAC,KAAK,CAAC,EAAE,EAAE,CAC5C,CAAC;QACF,OAAO,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;IAChE,CAAC;IAED,KAAK,CAAC,WAAW,CAAC,EAAU,EAAE,SAAS,GAAG,kBAAkB;QAC1D,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,GAAG,CACrC,cAAc,kBAAkB,CAAC,EAAE,CAAC,EAAE,CACvC,CAAC;QACF,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,gBAAgB,CAAC,EAAE,EAAE,SAAS,CAAC,CAAC;QAC1D,OAAO,EAAE,GAAG,WAAW,CAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IAC9C,CAAC;IAED,KAAK,CAAC,gBAAgB,CAAC,EAAU,EAAE,KAAK,GAAG,kBAAkB;QAC3D,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,GAAG,CACrC,cAAc,kBAAkB,CAAC,EAAE,CAAC,QAAQ,EAC5C,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,cAAc,CAAC,KAAK,CAAC,EAAE,EAAE,CAC5C,CAAC;QACF,OAAO,QAAQ,CAAC,KAAK;aAClB,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC;aACxC,MAAM,CAAC,CAAC,KAAK,EAAkB,EAAE,CAAC,KAAK,KAAK,IAAI,CAAC,CAAC;IACvD,CAAC;CACF;AAED,SAAS,gBAAgB,CAAC,IAAc,EAAE,KAAe;IACvD,MAAM,cAAc,GAAG,IAAI,CAAC,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,IAAI,EAAE,SAAS,EAAE;QACpE,WAAW,EAAE,MAAM;KACpB,CAAC,CAAC;IACH,IAAI,cAAc,KAAK,CAAC;QAAE,OAAO,cAAc,CAAC;IAChD,OAAO,IAAI,CAAC,EAAE,CAAC,aAAa,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;AACzC,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { SpotifyApi } from '../spotify/client.js';
|
|
2
|
+
export interface QueueItem {
|
|
3
|
+
name: string;
|
|
4
|
+
uri: string;
|
|
5
|
+
type: 'track' | 'episode';
|
|
6
|
+
subtitle: string;
|
|
7
|
+
durationMs: number;
|
|
8
|
+
}
|
|
9
|
+
export interface PlaybackQueue {
|
|
10
|
+
currentlyPlaying: QueueItem | null;
|
|
11
|
+
queue: QueueItem[];
|
|
12
|
+
}
|
|
13
|
+
export declare class QueueService {
|
|
14
|
+
private readonly spotify;
|
|
15
|
+
constructor(spotify: SpotifyApi);
|
|
16
|
+
getQueue(): Promise<PlaybackQueue>;
|
|
17
|
+
addItem(uri: string): Promise<void>;
|
|
18
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export class QueueService {
|
|
2
|
+
spotify;
|
|
3
|
+
constructor(spotify) {
|
|
4
|
+
this.spotify = spotify;
|
|
5
|
+
}
|
|
6
|
+
async getQueue() {
|
|
7
|
+
const response = await this.spotify.get('/me/player/queue');
|
|
8
|
+
return {
|
|
9
|
+
currentlyPlaying: response.currently_playing
|
|
10
|
+
? mapQueueItem(response.currently_playing)
|
|
11
|
+
: null,
|
|
12
|
+
queue: response.queue
|
|
13
|
+
.map(mapQueueItem)
|
|
14
|
+
.filter((item) => item !== null),
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
async addItem(uri) {
|
|
18
|
+
await this.spotify.post('/me/player/queue', { query: { uri } });
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function mapQueueItem(item) {
|
|
22
|
+
switch (item.type) {
|
|
23
|
+
case 'track':
|
|
24
|
+
return {
|
|
25
|
+
name: item.name,
|
|
26
|
+
uri: item.uri,
|
|
27
|
+
type: item.type,
|
|
28
|
+
subtitle: item.artists.map((artist) => artist.name).join(', '),
|
|
29
|
+
durationMs: item.duration_ms,
|
|
30
|
+
};
|
|
31
|
+
case 'episode':
|
|
32
|
+
return {
|
|
33
|
+
name: item.name,
|
|
34
|
+
uri: item.uri,
|
|
35
|
+
type: item.type,
|
|
36
|
+
subtitle: item.show.name,
|
|
37
|
+
durationMs: item.duration_ms,
|
|
38
|
+
};
|
|
39
|
+
default:
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=queue.service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"queue.service.js","sourceRoot":"","sources":["../../src/services/queue.service.ts"],"names":[],"mappings":"AAsCA,MAAM,OAAO,YAAY;IACM;IAA7B,YAA6B,OAAmB;QAAnB,YAAO,GAAP,OAAO,CAAY;IAAG,CAAC;IAEpD,KAAK,CAAC,QAAQ;QACZ,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,GAAG,CAAuB,kBAAkB,CAAC,CAAC;QAElF,OAAO;YACL,gBAAgB,EAAE,QAAQ,CAAC,iBAAiB;gBAC1C,CAAC,CAAC,YAAY,CAAC,QAAQ,CAAC,iBAAiB,CAAC;gBAC1C,CAAC,CAAC,IAAI;YACR,KAAK,EAAE,QAAQ,CAAC,KAAK;iBAClB,GAAG,CAAC,YAAY,CAAC;iBACjB,MAAM,CAAC,CAAC,IAAI,EAAqB,EAAE,CAAC,IAAI,KAAK,IAAI,CAAC;SACtD,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,GAAW;QACvB,MAAM,IAAI,CAAC,OAAO,CAAC,IAAI,CAAO,kBAAkB,EAAE,EAAE,KAAK,EAAE,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC;IACxE,CAAC;CACF;AAED,SAAS,YAAY,CAAC,IAAsB;IAC1C,QAAQ,IAAI,CAAC,IAAI,EAAE,CAAC;QAClB,KAAK,OAAO;YACV,OAAO;gBACL,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,GAAG,EAAE,IAAI,CAAC,GAAG;gBACb,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,QAAQ,EAAE,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;gBAC9D,UAAU,EAAE,IAAI,CAAC,WAAW;aAC7B,CAAC;QACJ,KAAK,SAAS;YACZ,OAAO;gBACL,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,GAAG,EAAE,IAAI,CAAC,GAAG;gBACb,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,QAAQ,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI;gBACxB,UAAU,EAAE,IAAI,CAAC,WAAW;aAC7B,CAAC;QACJ;YACE,OAAO,IAAI,CAAC;IAChB,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { SpotifyApi } from '../spotify/client.js';
|
|
2
|
+
import type { RecentlyPlayedTrack } from './models.js';
|
|
3
|
+
export declare class RecentService {
|
|
4
|
+
private readonly spotify;
|
|
5
|
+
constructor(spotify: SpotifyApi);
|
|
6
|
+
getRecentlyPlayed(limit?: number): Promise<RecentlyPlayedTrack[]>;
|
|
7
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { mapPlaybackItem, normalizeLimit } from './mappers.js';
|
|
2
|
+
const DEFAULT_RECENT_LIMIT = 20;
|
|
3
|
+
export class RecentService {
|
|
4
|
+
spotify;
|
|
5
|
+
constructor(spotify) {
|
|
6
|
+
this.spotify = spotify;
|
|
7
|
+
}
|
|
8
|
+
async getRecentlyPlayed(limit = DEFAULT_RECENT_LIMIT) {
|
|
9
|
+
const response = await this.spotify.get('/me/player/recently-played', { query: { limit: normalizeLimit(limit) } });
|
|
10
|
+
return response.items.flatMap(({ track, played_at: playedAt, context }) => {
|
|
11
|
+
const mapped = mapPlaybackItem(track);
|
|
12
|
+
if (!mapped)
|
|
13
|
+
return [];
|
|
14
|
+
return [
|
|
15
|
+
{
|
|
16
|
+
playedAt,
|
|
17
|
+
track: mapped,
|
|
18
|
+
...(context?.uri ? { contextUri: context.uri } : {}),
|
|
19
|
+
},
|
|
20
|
+
];
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=recent.service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recent.service.js","sourceRoot":"","sources":["../../src/services/recent.service.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAG/D,MAAM,oBAAoB,GAAG,EAAE,CAAC;AAEhC,MAAM,OAAO,aAAa;IACK;IAA7B,YAA6B,OAAmB;QAAnB,YAAO,GAAP,OAAO,CAAY;IAAG,CAAC;IAEpD,KAAK,CAAC,iBAAiB,CAAC,KAAK,GAAG,oBAAoB;QAClD,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,GAAG,CACrC,4BAA4B,EAC5B,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,cAAc,CAAC,KAAK,CAAC,EAAE,EAAE,CAC5C,CAAC;QACF,OAAO,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE,EAAE;YACxE,MAAM,MAAM,GAAG,eAAe,CAAC,KAAK,CAAC,CAAC;YACtC,IAAI,CAAC,MAAM;gBAAE,OAAO,EAAE,CAAC;YACvB,OAAO;gBACL;oBACE,QAAQ;oBACR,KAAK,EAAE,MAAM;oBACb,GAAG,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;iBACrD;aACF,CAAC;QACJ,CAAC,CAAC,CAAC;IACL,CAAC;CACF"}
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import type { SpotifyApi } from '../spotify/client.js';
|
|
2
|
-
import type { Track } from './models.js';
|
|
2
|
+
import type { Album, Artist, Playlist, Track } from './models.js';
|
|
3
3
|
export declare class SearchService {
|
|
4
4
|
private readonly spotify;
|
|
5
5
|
constructor(spotify: SpotifyApi);
|
|
6
6
|
searchTracks(query: string, limit?: number): Promise<Track[]>;
|
|
7
|
+
searchAlbums(query: string, limit?: number): Promise<Album[]>;
|
|
8
|
+
searchArtists(query: string, limit?: number): Promise<Artist[]>;
|
|
9
|
+
searchPlaylists(query: string, limit?: number): Promise<Playlist[]>;
|
|
10
|
+
private search;
|
|
7
11
|
}
|
|
@@ -1,28 +1,43 @@
|
|
|
1
|
+
import { mapAlbum, mapArtist, mapPlaylist, mapTrack, normalizeLimit } from './mappers.js';
|
|
2
|
+
const DEFAULT_SEARCH_LIMIT = 10;
|
|
1
3
|
export class SearchService {
|
|
2
4
|
spotify;
|
|
3
5
|
constructor(spotify) {
|
|
4
6
|
this.spotify = spotify;
|
|
5
7
|
}
|
|
6
|
-
async searchTracks(query, limit =
|
|
8
|
+
async searchTracks(query, limit = DEFAULT_SEARCH_LIMIT) {
|
|
9
|
+
const response = await this.search(query, 'track', limit);
|
|
10
|
+
return (response?.tracks?.items ?? [])
|
|
11
|
+
.map(mapTrack)
|
|
12
|
+
.filter((track) => track !== null);
|
|
13
|
+
}
|
|
14
|
+
async searchAlbums(query, limit = DEFAULT_SEARCH_LIMIT) {
|
|
15
|
+
const response = await this.search(query, 'album', limit);
|
|
16
|
+
return (response?.albums?.items ?? [])
|
|
17
|
+
.map(mapAlbum)
|
|
18
|
+
.filter((album) => album !== null);
|
|
19
|
+
}
|
|
20
|
+
async searchArtists(query, limit = DEFAULT_SEARCH_LIMIT) {
|
|
21
|
+
const response = await this.search(query, 'artist', limit);
|
|
22
|
+
return (response?.artists?.items ?? []).map(mapArtist);
|
|
23
|
+
}
|
|
24
|
+
async searchPlaylists(query, limit = DEFAULT_SEARCH_LIMIT) {
|
|
25
|
+
const response = await this.search(query, 'playlist', limit);
|
|
26
|
+
return (response?.playlists?.items ?? [])
|
|
27
|
+
.filter((playlist) => playlist !== null)
|
|
28
|
+
.map(mapPlaylist);
|
|
29
|
+
}
|
|
30
|
+
async search(query, type, limit) {
|
|
7
31
|
const normalizedQuery = query.trim();
|
|
8
32
|
if (!normalizedQuery)
|
|
9
|
-
return
|
|
10
|
-
|
|
11
|
-
query: {
|
|
33
|
+
return null;
|
|
34
|
+
return this.spotify.get('/search', {
|
|
35
|
+
query: {
|
|
36
|
+
q: normalizedQuery,
|
|
37
|
+
type,
|
|
38
|
+
limit: normalizeLimit(limit, DEFAULT_SEARCH_LIMIT, 10),
|
|
39
|
+
},
|
|
12
40
|
});
|
|
13
|
-
return response.tracks.items.map(mapTrack);
|
|
14
41
|
}
|
|
15
42
|
}
|
|
16
|
-
function mapTrack(track) {
|
|
17
|
-
const externalUrl = track.external_urls?.spotify;
|
|
18
|
-
return {
|
|
19
|
-
id: track.id,
|
|
20
|
-
uri: track.uri,
|
|
21
|
-
name: track.name,
|
|
22
|
-
artists: track.artists.map((artist) => artist.name),
|
|
23
|
-
album: track.album.name,
|
|
24
|
-
durationMs: track.duration_ms,
|
|
25
|
-
...(externalUrl ? { externalUrl } : {}),
|
|
26
|
-
};
|
|
27
|
-
}
|
|
28
43
|
//# sourceMappingURL=search.service.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"search.service.js","sourceRoot":"","sources":["../../src/services/search.service.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"search.service.js","sourceRoot":"","sources":["../../src/services/search.service.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,WAAW,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAG1F,MAAM,oBAAoB,GAAG,EAAE,CAAC;AAEhC,MAAM,OAAO,aAAa;IACK;IAA7B,YAA6B,OAAmB;QAAnB,YAAO,GAAP,OAAO,CAAY;IAAG,CAAC;IAEpD,KAAK,CAAC,YAAY,CAAC,KAAa,EAAE,KAAK,GAAG,oBAAoB;QAC5D,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,CAAC,CAAC;QAC1D,OAAO,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,IAAI,EAAE,CAAC;aACnC,GAAG,CAAC,QAAQ,CAAC;aACb,MAAM,CAAC,CAAC,KAAK,EAAkB,EAAE,CAAC,KAAK,KAAK,IAAI,CAAC,CAAC;IACvD,CAAC;IAED,KAAK,CAAC,YAAY,CAAC,KAAa,EAAE,KAAK,GAAG,oBAAoB;QAC5D,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,CAAC,CAAC;QAC1D,OAAO,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,IAAI,EAAE,CAAC;aACnC,GAAG,CAAC,QAAQ,CAAC;aACb,MAAM,CAAC,CAAC,KAAK,EAAkB,EAAE,CAAC,KAAK,KAAK,IAAI,CAAC,CAAC;IACvD,CAAC;IAED,KAAK,CAAC,aAAa,CAAC,KAAa,EAAE,KAAK,GAAG,oBAAoB;QAC7D,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,QAAQ,EAAE,KAAK,CAAC,CAAC;QAC3D,OAAO,CAAC,QAAQ,EAAE,OAAO,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;IACzD,CAAC;IAED,KAAK,CAAC,eAAe,CAAC,KAAa,EAAE,KAAK,GAAG,oBAAoB;QAC/D,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,UAAU,EAAE,KAAK,CAAC,CAAC;QAC7D,OAAO,CAAC,QAAQ,EAAE,SAAS,EAAE,KAAK,IAAI,EAAE,CAAC;aACtC,MAAM,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,KAAK,IAAI,CAAC;aACvC,GAAG,CAAC,WAAW,CAAC,CAAC;IACtB,CAAC;IAEO,KAAK,CAAC,MAAM,CAClB,KAAa,EACb,IAA+C,EAC/C,KAAa;QAEb,MAAM,eAAe,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;QACrC,IAAI,CAAC,eAAe;YAAE,OAAO,IAAI,CAAC;QAClC,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CAAwB,SAAS,EAAE;YACxD,KAAK,EAAE;gBACL,CAAC,EAAE,eAAe;gBAClB,IAAI;gBACJ,KAAK,EAAE,cAAc,CAAC,KAAK,EAAE,oBAAoB,EAAE,EAAE,CAAC;aACvD;SACF,CAAC,CAAC;IACL,CAAC;CACF"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { type UpdateCacheStore } from '../storage/update-cache.js';
|
|
2
|
+
export declare const SPOTI_PACKAGE_NAME = "@bzenky/spoti";
|
|
3
|
+
export declare const NPM_REGISTRY_URL = "https://registry.npmjs.org/%40bzenky%2Fspoti";
|
|
4
|
+
export declare const UPDATE_CHECK_INTERVAL_MS: number;
|
|
5
|
+
export declare const UPDATE_CHECK_TIMEOUT_MS = 3000;
|
|
6
|
+
export declare const UPDATE_COMMAND = "npm install -g @bzenky/spoti@latest";
|
|
7
|
+
export type UpdateStatus = 'current' | 'update-available';
|
|
8
|
+
export type UpdateCheckSource = 'cache' | 'registry';
|
|
9
|
+
export interface UpdateCheckResult {
|
|
10
|
+
status: UpdateStatus;
|
|
11
|
+
currentVersion: string;
|
|
12
|
+
latestVersion: string;
|
|
13
|
+
checkedAt: number;
|
|
14
|
+
source: UpdateCheckSource;
|
|
15
|
+
}
|
|
16
|
+
export interface UpdateChecker {
|
|
17
|
+
checkInBackground(): Promise<UpdateCheckResult | null>;
|
|
18
|
+
}
|
|
19
|
+
export interface UpdateCommandRunner {
|
|
20
|
+
run(command: string, args: readonly string[]): Promise<void>;
|
|
21
|
+
}
|
|
22
|
+
export interface UpdateServiceDependencies {
|
|
23
|
+
cache?: UpdateCacheStore;
|
|
24
|
+
fetch?: typeof globalThis.fetch;
|
|
25
|
+
now?: () => number;
|
|
26
|
+
timeoutMs?: number;
|
|
27
|
+
checkIntervalMs?: number;
|
|
28
|
+
commandRunner?: UpdateCommandRunner;
|
|
29
|
+
}
|
|
30
|
+
export declare class UpdateCheckError extends Error {
|
|
31
|
+
constructor(message: string, options?: ErrorOptions);
|
|
32
|
+
}
|
|
33
|
+
export declare class UpdateInstallError extends Error {
|
|
34
|
+
constructor(message: string, options?: ErrorOptions);
|
|
35
|
+
}
|
|
36
|
+
export declare class UpdateService implements UpdateChecker {
|
|
37
|
+
private readonly currentVersion;
|
|
38
|
+
private readonly cache;
|
|
39
|
+
private readonly fetchImplementation;
|
|
40
|
+
private readonly clock;
|
|
41
|
+
private readonly timeoutMs;
|
|
42
|
+
private readonly checkIntervalMs;
|
|
43
|
+
private readonly commandRunner;
|
|
44
|
+
constructor(currentVersion: string, dependencies?: UpdateServiceDependencies);
|
|
45
|
+
/** Always contacts npm and reports actionable failures. Intended for `spoti update --check`. */
|
|
46
|
+
checkForeground(): Promise<UpdateCheckResult>;
|
|
47
|
+
/** Uses the 24-hour cache and hides failures so normal commands remain unaffected. */
|
|
48
|
+
checkInBackground(): Promise<UpdateCheckResult | null>;
|
|
49
|
+
/** The caller must obtain explicit user confirmation before setting `confirmed` to true. */
|
|
50
|
+
installLatest(confirmed: boolean, version: string): Promise<void>;
|
|
51
|
+
private fetchLatest;
|
|
52
|
+
private recordFailedCheck;
|
|
53
|
+
}
|
|
54
|
+
export declare class NodeUpdateCommandRunner implements UpdateCommandRunner {
|
|
55
|
+
run(command: string, args: readonly string[]): Promise<void>;
|
|
56
|
+
}
|
|
57
|
+
export declare function compareSemanticVersions(left: string, right: string): number;
|