@lucaperret/tidal-cli 1.1.2 → 1.2.2
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 +13 -1
- package/dist/album.d.ts +4 -0
- package/dist/album.js +69 -50
- package/dist/artist.d.ts +7 -0
- package/dist/artist.js +139 -91
- package/dist/history.d.ts +3 -2
- package/dist/history.js +27 -17
- package/dist/index.js +96 -1
- package/dist/library.d.ts +25 -2
- package/dist/library.js +97 -48
- package/dist/mix.d.ts +4 -0
- package/dist/mix.js +59 -0
- package/dist/playback.d.ts +4 -0
- package/dist/playback.js +62 -46
- package/dist/playlist.d.ts +43 -0
- package/dist/playlist.js +183 -104
- package/dist/recommend.d.ts +4 -1
- package/dist/recommend.js +71 -19
- package/dist/saved.d.ts +16 -0
- package/dist/saved.js +107 -0
- package/dist/search-history.d.ts +13 -0
- package/dist/search-history.js +94 -0
- package/dist/search.d.ts +4 -2
- package/dist/search.js +66 -45
- package/dist/session.js +17 -7
- package/dist/share.d.ts +5 -0
- package/dist/share.js +53 -0
- package/dist/track.d.ts +14 -0
- package/dist/track.js +128 -91
- package/dist/types.d.ts +158 -0
- package/dist/types.js +4 -0
- package/dist/user.d.ts +3 -0
- package/dist/user.js +27 -18
- package/package.json +8 -6
package/README.md
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
[](https://www.npmjs.com/package/@lucaperret/tidal-cli)
|
|
10
10
|
[](https://github.com/lucaperret/tidal-cli/actions)
|
|
11
|
+
[](https://smithery.ai/servers/lucaperret/tidal)
|
|
11
12
|
[](LICENSE)
|
|
12
13
|
[](https://nodejs.org)
|
|
13
14
|
|
|
@@ -148,9 +149,20 @@ tidal-cli --json playlist list
|
|
|
148
149
|
tidal-cli --json artist similar 8992
|
|
149
150
|
```
|
|
150
151
|
|
|
152
|
+
## MCP Server (Claude Integration)
|
|
153
|
+
|
|
154
|
+
tidal-cli is available as a remote MCP server for [Claude Desktop](https://claude.ai), [Smithery](https://smithery.ai/servers/lucaperret/tidal), and any MCP-compatible client.
|
|
155
|
+
|
|
156
|
+
**Connect in Claude Desktop:**
|
|
157
|
+
1. Settings → Connectors → Add custom connector
|
|
158
|
+
2. Enter: `https://tidal-cli.lucaperret.ch/api/mcp`
|
|
159
|
+
3. Click "Connect" → log in to Tidal → done
|
|
160
|
+
|
|
161
|
+
32 tools with OAuth authentication, safety annotations, and 3 prompt templates.
|
|
162
|
+
|
|
151
163
|
## Agent Automation
|
|
152
164
|
|
|
153
|
-
tidal-cli is available as an [OpenClaw](https://openclaw.ai) skill on [ClawHub](https://clawhub.ai/lucaperret/tidal-cli). Install it for your AI agent:
|
|
165
|
+
tidal-cli is also available as an [OpenClaw](https://openclaw.ai) skill on [ClawHub](https://clawhub.ai/lucaperret/tidal-cli). Install it for your AI agent:
|
|
154
166
|
|
|
155
167
|
```bash
|
|
156
168
|
clawhub install tidal-cli
|
package/dist/album.d.ts
CHANGED
|
@@ -1,2 +1,6 @@
|
|
|
1
|
+
import type { AlbumInfo, AlbumResult } from './types';
|
|
2
|
+
export type { AlbumInfo, AlbumResult };
|
|
3
|
+
export declare function getAlbumInfoData(albumId: string, client: any, countryCode: string): Promise<AlbumInfo>;
|
|
1
4
|
export declare function getAlbumInfo(albumId: string, json: boolean): Promise<void>;
|
|
5
|
+
export declare function getAlbumByBarcodeData(barcode: string, client: any, countryCode: string): Promise<AlbumResult[]>;
|
|
2
6
|
export declare function getAlbumByBarcode(barcode: string, json: boolean): Promise<void>;
|
package/dist/album.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getAlbumInfoData = getAlbumInfoData;
|
|
3
4
|
exports.getAlbumInfo = getAlbumInfo;
|
|
5
|
+
exports.getAlbumByBarcodeData = getAlbumByBarcodeData;
|
|
4
6
|
exports.getAlbumByBarcode = getAlbumByBarcode;
|
|
5
7
|
const auth_1 = require("./auth");
|
|
6
8
|
function formatDuration(isoDuration) {
|
|
@@ -14,32 +16,29 @@ function formatDuration(isoDuration) {
|
|
|
14
16
|
const s = (match[3] ?? '0').padStart(2, '0');
|
|
15
17
|
return `${h}${m}:${s}`;
|
|
16
18
|
}
|
|
17
|
-
async function
|
|
18
|
-
const client = await (0, auth_1.getApiClient)();
|
|
19
|
+
async function getAlbumInfoData(albumId, client, countryCode) {
|
|
19
20
|
const { data, error } = await client.GET('/albums/{id}', {
|
|
20
21
|
params: {
|
|
21
22
|
path: { id: albumId },
|
|
22
23
|
query: {
|
|
23
|
-
countryCode
|
|
24
|
+
countryCode,
|
|
24
25
|
include: ['artists', 'coverArt'],
|
|
25
26
|
},
|
|
26
27
|
},
|
|
27
28
|
});
|
|
28
29
|
if (error || !data) {
|
|
29
|
-
|
|
30
|
-
process.exit(1);
|
|
30
|
+
throw new Error(`Failed to get album info — ${JSON.stringify(error)}`);
|
|
31
31
|
}
|
|
32
32
|
const attrs = data.data?.attributes ?? {};
|
|
33
33
|
const included = data.included ?? [];
|
|
34
34
|
const artists = included
|
|
35
35
|
.filter((item) => item.type === 'artists')
|
|
36
36
|
.map((item) => item.attributes?.name ?? item.id);
|
|
37
|
-
// Get cover art from included artworks
|
|
38
37
|
const artwork = included.find((item) => item.type === 'artworks');
|
|
39
38
|
const files = artwork?.attributes?.files ?? [];
|
|
40
39
|
const preferred = files.find((f) => f.meta?.width === 640) ?? files[0];
|
|
41
40
|
const coverUrl = preferred?.href;
|
|
42
|
-
|
|
41
|
+
return {
|
|
43
42
|
id: albumId,
|
|
44
43
|
title: attrs.title ?? 'Unknown',
|
|
45
44
|
artists,
|
|
@@ -52,47 +51,56 @@ async function getAlbumInfo(albumId, json) {
|
|
|
52
51
|
barcodeId: attrs.barcodeId,
|
|
53
52
|
coverUrl,
|
|
54
53
|
};
|
|
55
|
-
if (json) {
|
|
56
|
-
console.log(JSON.stringify(result, null, 2));
|
|
57
|
-
return;
|
|
58
|
-
}
|
|
59
|
-
console.log(`\nAlbum: [${result.id}] ${result.title}`);
|
|
60
|
-
if (result.artists.length > 0)
|
|
61
|
-
console.log(` Artists: ${result.artists.join(', ')}`);
|
|
62
|
-
if (result.albumType)
|
|
63
|
-
console.log(` Type: ${result.albumType}`);
|
|
64
|
-
if (result.releaseDate)
|
|
65
|
-
console.log(` Release Date: ${result.releaseDate}`);
|
|
66
|
-
if (result.numberOfItems !== undefined)
|
|
67
|
-
console.log(` Tracks: ${result.numberOfItems}`);
|
|
68
|
-
if (result.duration)
|
|
69
|
-
console.log(` Duration: ${result.duration}`);
|
|
70
|
-
if (result.popularity !== undefined)
|
|
71
|
-
console.log(` Popularity: ${result.popularity}`);
|
|
72
|
-
if (result.explicit !== undefined)
|
|
73
|
-
console.log(` Explicit: ${result.explicit}`);
|
|
74
|
-
if (result.barcodeId)
|
|
75
|
-
console.log(` Barcode: ${result.barcodeId}`);
|
|
76
|
-
if (result.coverUrl)
|
|
77
|
-
console.log(` Cover: ${result.coverUrl}`);
|
|
78
|
-
console.log();
|
|
79
54
|
}
|
|
80
|
-
async function
|
|
55
|
+
async function getAlbumInfo(albumId, json) {
|
|
81
56
|
const client = await (0, auth_1.getApiClient)();
|
|
57
|
+
const countryCode = await (0, auth_1.getCountryCode)();
|
|
58
|
+
try {
|
|
59
|
+
const result = await getAlbumInfoData(albumId, client, countryCode);
|
|
60
|
+
if (json) {
|
|
61
|
+
console.log(JSON.stringify(result, null, 2));
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
console.log(`\nAlbum: [${result.id}] ${result.title}`);
|
|
65
|
+
if (result.artists.length > 0)
|
|
66
|
+
console.log(` Artists: ${result.artists.join(', ')}`);
|
|
67
|
+
if (result.albumType)
|
|
68
|
+
console.log(` Type: ${result.albumType}`);
|
|
69
|
+
if (result.releaseDate)
|
|
70
|
+
console.log(` Release Date: ${result.releaseDate}`);
|
|
71
|
+
if (result.numberOfItems !== undefined)
|
|
72
|
+
console.log(` Tracks: ${result.numberOfItems}`);
|
|
73
|
+
if (result.duration)
|
|
74
|
+
console.log(` Duration: ${result.duration}`);
|
|
75
|
+
if (result.popularity !== undefined)
|
|
76
|
+
console.log(` Popularity: ${result.popularity}`);
|
|
77
|
+
if (result.explicit !== undefined)
|
|
78
|
+
console.log(` Explicit: ${result.explicit}`);
|
|
79
|
+
if (result.barcodeId)
|
|
80
|
+
console.log(` Barcode: ${result.barcodeId}`);
|
|
81
|
+
if (result.coverUrl)
|
|
82
|
+
console.log(` Cover: ${result.coverUrl}`);
|
|
83
|
+
console.log();
|
|
84
|
+
}
|
|
85
|
+
catch (err) {
|
|
86
|
+
console.error(`Error: ${err.message}`);
|
|
87
|
+
process.exit(1);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
async function getAlbumByBarcodeData(barcode, client, countryCode) {
|
|
82
91
|
const { data, error } = await client.GET('/albums', {
|
|
83
92
|
params: {
|
|
84
93
|
query: {
|
|
85
|
-
countryCode
|
|
94
|
+
countryCode,
|
|
86
95
|
'filter[barcodeId]': [barcode],
|
|
87
96
|
},
|
|
88
97
|
},
|
|
89
98
|
});
|
|
90
99
|
if (error || !data) {
|
|
91
|
-
|
|
92
|
-
process.exit(1);
|
|
100
|
+
throw new Error(`Failed to get album by barcode — ${JSON.stringify(error)}`);
|
|
93
101
|
}
|
|
94
102
|
const items = data.data ?? [];
|
|
95
|
-
|
|
103
|
+
return items.map((item) => {
|
|
96
104
|
const attrs = item.attributes;
|
|
97
105
|
return {
|
|
98
106
|
id: item.id,
|
|
@@ -104,21 +112,32 @@ async function getAlbumByBarcode(barcode, json) {
|
|
|
104
112
|
barcodeId: attrs?.barcodeId,
|
|
105
113
|
};
|
|
106
114
|
});
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
115
|
+
}
|
|
116
|
+
async function getAlbumByBarcode(barcode, json) {
|
|
117
|
+
const client = await (0, auth_1.getApiClient)();
|
|
118
|
+
const countryCode = await (0, auth_1.getCountryCode)();
|
|
119
|
+
try {
|
|
120
|
+
const albums = await getAlbumByBarcodeData(barcode, client, countryCode);
|
|
121
|
+
if (json) {
|
|
122
|
+
console.log(JSON.stringify(albums, null, 2));
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
if (albums.length === 0) {
|
|
126
|
+
console.log(`No albums found for barcode ${barcode}.`);
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
console.log(`\nAlbums matching barcode ${barcode}:\n`);
|
|
130
|
+
for (const a of albums) {
|
|
131
|
+
const extras = [a.albumType, a.releaseDate, a.numberOfItems !== undefined ? `${a.numberOfItems} tracks` : undefined]
|
|
132
|
+
.filter(Boolean)
|
|
133
|
+
.join(', ');
|
|
134
|
+
console.log(` [${a.id}] ${a.title}${extras ? ` (${extras})` : ''}`);
|
|
135
|
+
}
|
|
136
|
+
console.log();
|
|
114
137
|
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
.filter(Boolean)
|
|
119
|
-
.join(', ');
|
|
120
|
-
console.log(` [${a.id}] ${a.title}${extras ? ` (${extras})` : ''}`);
|
|
138
|
+
catch (err) {
|
|
139
|
+
console.error(`Error: ${err.message}`);
|
|
140
|
+
process.exit(1);
|
|
121
141
|
}
|
|
122
|
-
console.log();
|
|
123
142
|
}
|
|
124
143
|
//# sourceMappingURL=album.js.map
|
package/dist/artist.d.ts
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
|
+
import type { ArtistInfo, ArtistTrack, ArtistAlbum, SimilarArtist, RadioPlaylist } from './types';
|
|
2
|
+
export type { ArtistInfo, ArtistTrack, ArtistAlbum, SimilarArtist };
|
|
3
|
+
export declare function getArtistInfoData(artistId: string, client: any, countryCode: string): Promise<ArtistInfo>;
|
|
1
4
|
export declare function getArtistInfo(artistId: string, json: boolean): Promise<void>;
|
|
5
|
+
export declare function getArtistRadioData(artistId: string, client: any, countryCode: string): Promise<RadioPlaylist[]>;
|
|
2
6
|
export declare function getArtistRadio(artistId: string, json: boolean): Promise<void>;
|
|
7
|
+
export declare function getArtistTracksData(artistId: string, client: any, countryCode: string): Promise<ArtistTrack[]>;
|
|
3
8
|
export declare function getArtistTracks(artistId: string, json: boolean): Promise<void>;
|
|
9
|
+
export declare function getArtistAlbumsData(artistId: string, client: any, countryCode: string): Promise<ArtistAlbum[]>;
|
|
4
10
|
export declare function getArtistAlbums(artistId: string, json: boolean): Promise<void>;
|
|
11
|
+
export declare function getSimilarArtistsData(artistId: string, client: any, countryCode: string): Promise<SimilarArtist[]>;
|
|
5
12
|
export declare function getSimilarArtists(artistId: string, json: boolean): Promise<void>;
|
package/dist/artist.js
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getArtistInfoData = getArtistInfoData;
|
|
3
4
|
exports.getArtistInfo = getArtistInfo;
|
|
5
|
+
exports.getArtistRadioData = getArtistRadioData;
|
|
4
6
|
exports.getArtistRadio = getArtistRadio;
|
|
7
|
+
exports.getArtistTracksData = getArtistTracksData;
|
|
5
8
|
exports.getArtistTracks = getArtistTracks;
|
|
9
|
+
exports.getArtistAlbumsData = getArtistAlbumsData;
|
|
6
10
|
exports.getArtistAlbums = getArtistAlbums;
|
|
11
|
+
exports.getSimilarArtistsData = getSimilarArtistsData;
|
|
7
12
|
exports.getSimilarArtists = getSimilarArtists;
|
|
8
13
|
const auth_1 = require("./auth");
|
|
9
14
|
function formatDuration(isoDuration) {
|
|
@@ -17,64 +22,70 @@ function formatDuration(isoDuration) {
|
|
|
17
22
|
const s = (match[3] ?? '0').padStart(2, '0');
|
|
18
23
|
return `${h}${m}:${s}`;
|
|
19
24
|
}
|
|
20
|
-
async function
|
|
21
|
-
const client = await (0, auth_1.getApiClient)();
|
|
25
|
+
async function getArtistInfoData(artistId, client, countryCode) {
|
|
22
26
|
const { data, error } = await client.GET('/artists/{id}', {
|
|
23
27
|
params: {
|
|
24
28
|
path: { id: artistId },
|
|
25
29
|
query: {
|
|
26
|
-
countryCode
|
|
30
|
+
countryCode,
|
|
27
31
|
include: ['biography'],
|
|
28
32
|
},
|
|
29
33
|
},
|
|
30
34
|
});
|
|
31
35
|
if (error || !data) {
|
|
32
|
-
|
|
33
|
-
process.exit(1);
|
|
36
|
+
throw new Error(`Failed to get artist info — ${JSON.stringify(error)}`);
|
|
34
37
|
}
|
|
35
38
|
const attrs = data.data?.attributes ?? {};
|
|
36
39
|
const included = data.included ?? [];
|
|
37
40
|
const biographyItem = included.find((item) => item.type === 'artistBiographies');
|
|
38
41
|
const biographyText = biographyItem?.attributes?.text ?? attrs.biography?.text ?? attrs.biography;
|
|
39
|
-
|
|
42
|
+
return {
|
|
40
43
|
id: artistId,
|
|
41
44
|
name: attrs.name ?? 'Unknown',
|
|
42
45
|
popularity: attrs.popularity,
|
|
43
46
|
handle: attrs.handle,
|
|
44
47
|
biography: biographyText,
|
|
45
48
|
};
|
|
46
|
-
if (json) {
|
|
47
|
-
console.log(JSON.stringify(result, null, 2));
|
|
48
|
-
return;
|
|
49
|
-
}
|
|
50
|
-
console.log(`\nArtist: [${result.id}] ${result.name}`);
|
|
51
|
-
if (result.handle)
|
|
52
|
-
console.log(` Handle: ${result.handle}`);
|
|
53
|
-
if (result.popularity !== undefined)
|
|
54
|
-
console.log(` Popularity: ${result.popularity}`);
|
|
55
|
-
if (result.biography)
|
|
56
|
-
console.log(` Biography: ${result.biography}`);
|
|
57
|
-
console.log();
|
|
58
49
|
}
|
|
59
|
-
async function
|
|
50
|
+
async function getArtistInfo(artistId, json) {
|
|
60
51
|
const client = await (0, auth_1.getApiClient)();
|
|
52
|
+
const countryCode = await (0, auth_1.getCountryCode)();
|
|
53
|
+
try {
|
|
54
|
+
const result = await getArtistInfoData(artistId, client, countryCode);
|
|
55
|
+
if (json) {
|
|
56
|
+
console.log(JSON.stringify(result, null, 2));
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
console.log(`\nArtist: [${result.id}] ${result.name}`);
|
|
60
|
+
if (result.handle)
|
|
61
|
+
console.log(` Handle: ${result.handle}`);
|
|
62
|
+
if (result.popularity !== undefined)
|
|
63
|
+
console.log(` Popularity: ${result.popularity}`);
|
|
64
|
+
if (result.biography)
|
|
65
|
+
console.log(` Biography: ${result.biography}`);
|
|
66
|
+
console.log();
|
|
67
|
+
}
|
|
68
|
+
catch (err) {
|
|
69
|
+
console.error(`Error: ${err.message}`);
|
|
70
|
+
process.exit(1);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
async function getArtistRadioData(artistId, client, countryCode) {
|
|
61
74
|
const { data, error } = await client.GET('/artists/{id}/relationships/radio', {
|
|
62
75
|
params: {
|
|
63
76
|
path: { id: artistId },
|
|
64
77
|
query: {
|
|
65
|
-
countryCode
|
|
78
|
+
countryCode,
|
|
66
79
|
include: ['radio'],
|
|
67
80
|
},
|
|
68
81
|
},
|
|
69
82
|
});
|
|
70
83
|
if (error || !data) {
|
|
71
|
-
|
|
72
|
-
process.exit(1);
|
|
84
|
+
throw new Error(`Failed to get artist radio — ${JSON.stringify(error)}`);
|
|
73
85
|
}
|
|
74
|
-
// Radio returns playlists (mix playlists), not individual tracks
|
|
75
86
|
const radioData = data.data ?? [];
|
|
76
87
|
const included = data.included ?? [];
|
|
77
|
-
|
|
88
|
+
return radioData.map((item) => {
|
|
78
89
|
const incl = included.find((i) => i.id === item.id && i.type === 'playlists');
|
|
79
90
|
const attrs = incl?.attributes ?? {};
|
|
80
91
|
return {
|
|
@@ -82,83 +93,100 @@ async function getArtistRadio(artistId, json) {
|
|
|
82
93
|
type: item.type,
|
|
83
94
|
name: attrs.name,
|
|
84
95
|
numberOfItems: attrs.numberOfItems,
|
|
85
|
-
description: attrs.description,
|
|
86
96
|
};
|
|
87
97
|
});
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
98
|
+
}
|
|
99
|
+
async function getArtistRadio(artistId, json) {
|
|
100
|
+
const client = await (0, auth_1.getApiClient)();
|
|
101
|
+
const countryCode = await (0, auth_1.getCountryCode)();
|
|
102
|
+
try {
|
|
103
|
+
const playlists = await getArtistRadioData(artistId, client, countryCode);
|
|
104
|
+
if (json) {
|
|
105
|
+
console.log(JSON.stringify(playlists, null, 2));
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
if (playlists.length === 0) {
|
|
109
|
+
console.log(`No radio found for artist ${artistId}.`);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
console.log(`\nRadio for artist ${artistId}:\n`);
|
|
113
|
+
for (const p of playlists) {
|
|
114
|
+
console.log(` [${p.id}] ${p.name ?? 'Radio Mix'}${p.numberOfItems ? ` (${p.numberOfItems} tracks)` : ''}`);
|
|
115
|
+
}
|
|
116
|
+
console.log();
|
|
95
117
|
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
118
|
+
catch (err) {
|
|
119
|
+
console.error(`Error: ${err.message}`);
|
|
120
|
+
process.exit(1);
|
|
99
121
|
}
|
|
100
|
-
console.log();
|
|
101
122
|
}
|
|
102
|
-
async function
|
|
103
|
-
const client = await (0, auth_1.getApiClient)();
|
|
123
|
+
async function getArtistTracksData(artistId, client, countryCode) {
|
|
104
124
|
const { data, error } = await client.GET('/artists/{id}/relationships/tracks', {
|
|
105
125
|
params: {
|
|
106
126
|
path: { id: artistId },
|
|
107
127
|
query: {
|
|
108
|
-
countryCode
|
|
128
|
+
countryCode,
|
|
109
129
|
'collapseBy': 'FINGERPRINT',
|
|
110
130
|
include: ['tracks'],
|
|
111
131
|
},
|
|
112
132
|
},
|
|
113
133
|
});
|
|
114
134
|
if (error || !data) {
|
|
115
|
-
|
|
116
|
-
process.exit(1);
|
|
135
|
+
throw new Error(`Failed to get artist tracks — ${JSON.stringify(error)}`);
|
|
117
136
|
}
|
|
118
137
|
const included = data.included ?? [];
|
|
119
|
-
|
|
138
|
+
return included
|
|
120
139
|
.filter((item) => item.type === 'tracks')
|
|
121
140
|
.map((item) => {
|
|
122
141
|
const attrs = item.attributes;
|
|
123
142
|
return {
|
|
124
143
|
id: item.id,
|
|
125
144
|
title: attrs?.title ?? 'Unknown',
|
|
126
|
-
duration: attrs?.duration,
|
|
145
|
+
duration: formatDuration(attrs?.duration),
|
|
127
146
|
isrc: attrs?.isrc,
|
|
128
147
|
popularity: attrs?.popularity,
|
|
129
148
|
};
|
|
130
149
|
});
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
150
|
+
}
|
|
151
|
+
async function getArtistTracks(artistId, json) {
|
|
152
|
+
const client = await (0, auth_1.getApiClient)();
|
|
153
|
+
const countryCode = await (0, auth_1.getCountryCode)();
|
|
154
|
+
try {
|
|
155
|
+
const tracks = await getArtistTracksData(artistId, client, countryCode);
|
|
156
|
+
if (json) {
|
|
157
|
+
console.log(JSON.stringify(tracks, null, 2));
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
if (tracks.length === 0) {
|
|
161
|
+
console.log(`No tracks found for artist ${artistId}.`);
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
console.log(`\nTracks for artist ${artistId}:\n`);
|
|
165
|
+
for (const t of tracks) {
|
|
166
|
+
console.log(` [${t.id}] ${t.title}${t.popularity !== undefined ? ` (popularity: ${t.popularity})` : ''}`);
|
|
167
|
+
}
|
|
168
|
+
console.log();
|
|
138
169
|
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
170
|
+
catch (err) {
|
|
171
|
+
console.error(`Error: ${err.message}`);
|
|
172
|
+
process.exit(1);
|
|
142
173
|
}
|
|
143
|
-
console.log();
|
|
144
174
|
}
|
|
145
|
-
async function
|
|
146
|
-
const client = await (0, auth_1.getApiClient)();
|
|
175
|
+
async function getArtistAlbumsData(artistId, client, countryCode) {
|
|
147
176
|
const { data, error } = await client.GET('/artists/{id}/relationships/albums', {
|
|
148
177
|
params: {
|
|
149
178
|
path: { id: artistId },
|
|
150
179
|
query: {
|
|
151
|
-
countryCode
|
|
180
|
+
countryCode,
|
|
152
181
|
include: ['albums'],
|
|
153
182
|
},
|
|
154
183
|
},
|
|
155
184
|
});
|
|
156
185
|
if (error || !data) {
|
|
157
|
-
|
|
158
|
-
process.exit(1);
|
|
186
|
+
throw new Error(`Failed to get artist albums — ${JSON.stringify(error)}`);
|
|
159
187
|
}
|
|
160
188
|
const included = data.included ?? [];
|
|
161
|
-
|
|
189
|
+
return included
|
|
162
190
|
.filter((item) => item.type === 'albums')
|
|
163
191
|
.map((item) => {
|
|
164
192
|
const attrs = item.attributes;
|
|
@@ -170,40 +198,49 @@ async function getArtistAlbums(artistId, json) {
|
|
|
170
198
|
numberOfItems: attrs?.numberOfItems,
|
|
171
199
|
};
|
|
172
200
|
});
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
201
|
+
}
|
|
202
|
+
async function getArtistAlbums(artistId, json) {
|
|
203
|
+
const client = await (0, auth_1.getApiClient)();
|
|
204
|
+
const countryCode = await (0, auth_1.getCountryCode)();
|
|
205
|
+
try {
|
|
206
|
+
const albums = await getArtistAlbumsData(artistId, client, countryCode);
|
|
207
|
+
if (json) {
|
|
208
|
+
console.log(JSON.stringify(albums, null, 2));
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
if (albums.length === 0) {
|
|
212
|
+
console.log(`No albums found for artist ${artistId}.`);
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
console.log(`\nAlbums for artist ${artistId}:\n`);
|
|
216
|
+
for (const a of albums) {
|
|
217
|
+
const extras = [a.albumType, a.releaseDate, a.numberOfItems !== undefined ? `${a.numberOfItems} tracks` : undefined]
|
|
218
|
+
.filter(Boolean)
|
|
219
|
+
.join(', ');
|
|
220
|
+
console.log(` [${a.id}] ${a.title}${extras ? ` (${extras})` : ''}`);
|
|
221
|
+
}
|
|
222
|
+
console.log();
|
|
180
223
|
}
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
.filter(Boolean)
|
|
185
|
-
.join(', ');
|
|
186
|
-
console.log(` [${a.id}] ${a.title}${extras ? ` (${extras})` : ''}`);
|
|
224
|
+
catch (err) {
|
|
225
|
+
console.error(`Error: ${err.message}`);
|
|
226
|
+
process.exit(1);
|
|
187
227
|
}
|
|
188
|
-
console.log();
|
|
189
228
|
}
|
|
190
|
-
async function
|
|
191
|
-
const client = await (0, auth_1.getApiClient)();
|
|
229
|
+
async function getSimilarArtistsData(artistId, client, countryCode) {
|
|
192
230
|
const { data, error } = await client.GET('/artists/{id}/relationships/similarArtists', {
|
|
193
231
|
params: {
|
|
194
232
|
path: { id: artistId },
|
|
195
233
|
query: {
|
|
196
|
-
countryCode
|
|
234
|
+
countryCode,
|
|
197
235
|
include: ['similarArtists'],
|
|
198
236
|
},
|
|
199
237
|
},
|
|
200
238
|
});
|
|
201
239
|
if (error || !data) {
|
|
202
|
-
|
|
203
|
-
process.exit(1);
|
|
240
|
+
throw new Error(`Failed to get similar artists — ${JSON.stringify(error)}`);
|
|
204
241
|
}
|
|
205
242
|
const included = data.included ?? [];
|
|
206
|
-
|
|
243
|
+
return included
|
|
207
244
|
.filter((item) => item.type === 'artists')
|
|
208
245
|
.map((item) => {
|
|
209
246
|
const attrs = item.attributes;
|
|
@@ -213,18 +250,29 @@ async function getSimilarArtists(artistId, json) {
|
|
|
213
250
|
popularity: attrs?.popularity,
|
|
214
251
|
};
|
|
215
252
|
});
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
253
|
+
}
|
|
254
|
+
async function getSimilarArtists(artistId, json) {
|
|
255
|
+
const client = await (0, auth_1.getApiClient)();
|
|
256
|
+
const countryCode = await (0, auth_1.getCountryCode)();
|
|
257
|
+
try {
|
|
258
|
+
const artists = await getSimilarArtistsData(artistId, client, countryCode);
|
|
259
|
+
if (json) {
|
|
260
|
+
console.log(JSON.stringify(artists, null, 2));
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
if (artists.length === 0) {
|
|
264
|
+
console.log(`No similar artists found for artist ${artistId}.`);
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
console.log(`\nSimilar artists to ${artistId}:\n`);
|
|
268
|
+
for (const a of artists) {
|
|
269
|
+
console.log(` [${a.id}] ${a.name}${a.popularity !== undefined ? ` (popularity: ${a.popularity})` : ''}`);
|
|
270
|
+
}
|
|
271
|
+
console.log();
|
|
223
272
|
}
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
273
|
+
catch (err) {
|
|
274
|
+
console.error(`Error: ${err.message}`);
|
|
275
|
+
process.exit(1);
|
|
227
276
|
}
|
|
228
|
-
console.log();
|
|
229
277
|
}
|
|
230
278
|
//# sourceMappingURL=artist.js.map
|
package/dist/history.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
-
type
|
|
1
|
+
import type { RecentItem, RecentType } from './types';
|
|
2
|
+
export type { RecentItem, RecentType };
|
|
3
|
+
export declare function getRecentlyAddedData(type: RecentType, client: any, countryCode: string): Promise<RecentItem[]>;
|
|
2
4
|
export declare function getRecentlyAdded(type: RecentType, json: boolean): Promise<void>;
|
|
3
|
-
export {};
|
package/dist/history.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getRecentlyAddedData = getRecentlyAddedData;
|
|
3
4
|
exports.getRecentlyAdded = getRecentlyAdded;
|
|
4
5
|
const auth_1 = require("./auth");
|
|
5
6
|
const endpointMap = {
|
|
@@ -12,9 +13,7 @@ const includeTypeMap = {
|
|
|
12
13
|
albums: 'albums',
|
|
13
14
|
artists: 'artists',
|
|
14
15
|
};
|
|
15
|
-
async function
|
|
16
|
-
const client = await (0, auth_1.getApiClient)();
|
|
17
|
-
const countryCode = await (0, auth_1.getCountryCode)();
|
|
16
|
+
async function getRecentlyAddedData(type, client, countryCode) {
|
|
18
17
|
const { data, error } = await client.GET(endpointMap[type], {
|
|
19
18
|
params: {
|
|
20
19
|
path: { id: 'me' },
|
|
@@ -26,8 +25,7 @@ async function getRecentlyAdded(type, json) {
|
|
|
26
25
|
},
|
|
27
26
|
});
|
|
28
27
|
if (error || !data) {
|
|
29
|
-
|
|
30
|
-
process.exit(1);
|
|
28
|
+
throw new Error(`Failed to get recently added ${type} — ${JSON.stringify(error)}`);
|
|
31
29
|
}
|
|
32
30
|
const included = data.included ?? [];
|
|
33
31
|
const items = included
|
|
@@ -51,19 +49,31 @@ async function getRecentlyAdded(type, json) {
|
|
|
51
49
|
}
|
|
52
50
|
}
|
|
53
51
|
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
52
|
+
return items;
|
|
53
|
+
}
|
|
54
|
+
async function getRecentlyAdded(type, json) {
|
|
55
|
+
const client = await (0, auth_1.getApiClient)();
|
|
56
|
+
const countryCode = await (0, auth_1.getCountryCode)();
|
|
57
|
+
try {
|
|
58
|
+
const items = await getRecentlyAddedData(type, client, countryCode);
|
|
59
|
+
if (json) {
|
|
60
|
+
console.log(JSON.stringify(items, null, 2));
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
if (items.length === 0) {
|
|
64
|
+
console.log(`No recently added ${type} found.`);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
console.log(`\nRecently added ${type}:\n`);
|
|
68
|
+
for (const item of items) {
|
|
69
|
+
const date = item.addedAt ? ` (added: ${item.addedAt})` : '';
|
|
70
|
+
console.log(` [${item.id}] ${item.name}${date}`);
|
|
71
|
+
}
|
|
72
|
+
console.log();
|
|
61
73
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
console.log(` [${item.id}] ${item.name}${date}`);
|
|
74
|
+
catch (err) {
|
|
75
|
+
console.error(`Error: ${err.message}`);
|
|
76
|
+
process.exit(1);
|
|
66
77
|
}
|
|
67
|
-
console.log();
|
|
68
78
|
}
|
|
69
79
|
//# sourceMappingURL=history.js.map
|