@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/dist/saved.js ADDED
@@ -0,0 +1,107 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.listSavedItemsData = listSavedItemsData;
4
+ exports.listSavedItems = listSavedItems;
5
+ exports.addSavedItemData = addSavedItemData;
6
+ exports.addSavedItem = addSavedItem;
7
+ exports.removeSavedItemData = removeSavedItemData;
8
+ exports.removeSavedItem = removeSavedItem;
9
+ const auth_1 = require("./auth");
10
+ async function listSavedItemsData(client) {
11
+ const { data, error } = await client.GET('/userCollectionSaveForLaters/{id}/relationships/items', {
12
+ params: {
13
+ path: { id: 'me' },
14
+ query: { include: ['items'] },
15
+ },
16
+ });
17
+ if (error || !data) {
18
+ throw new Error(`Failed to list saved items — ${JSON.stringify(error)}`);
19
+ }
20
+ const included = data.included ?? [];
21
+ return included.map((item) => {
22
+ const attrs = item.attributes ?? {};
23
+ return {
24
+ id: item.id,
25
+ type: item.type,
26
+ name: attrs.title ?? attrs.name ?? 'Untitled',
27
+ };
28
+ });
29
+ }
30
+ async function listSavedItems(json) {
31
+ const client = await (0, auth_1.getApiClient)();
32
+ try {
33
+ const items = await listSavedItemsData(client);
34
+ if (json) {
35
+ console.log(JSON.stringify(items, null, 2));
36
+ return;
37
+ }
38
+ if (items.length === 0) {
39
+ console.log('No saved items found.');
40
+ return;
41
+ }
42
+ console.log('\nSaved for later:\n');
43
+ for (const item of items) {
44
+ console.log(` [${item.id}] (${item.type}) ${item.name}`);
45
+ }
46
+ console.log();
47
+ }
48
+ catch (err) {
49
+ console.error(`Error: ${err.message}`);
50
+ process.exit(1);
51
+ }
52
+ }
53
+ async function addSavedItemData(itemType, itemId, client) {
54
+ const { error } = await client.POST('/userCollectionSaveForLaters/{id}/relationships/items', {
55
+ params: { path: { id: 'me' } },
56
+ body: {
57
+ data: [{ id: itemId, type: itemType }],
58
+ },
59
+ });
60
+ if (error) {
61
+ throw new Error(`Failed to save item — ${JSON.stringify(error)}`);
62
+ }
63
+ return { id: itemId, type: itemType, added: true };
64
+ }
65
+ async function addSavedItem(itemType, itemId, json) {
66
+ const client = await (0, auth_1.getApiClient)();
67
+ try {
68
+ const result = await addSavedItemData(itemType, itemId, client);
69
+ if (json) {
70
+ console.log(JSON.stringify(result, null, 2));
71
+ return;
72
+ }
73
+ console.log(`\n${itemType} ${itemId} saved for later.`);
74
+ }
75
+ catch (err) {
76
+ console.error(`Error: ${err.message}`);
77
+ process.exit(1);
78
+ }
79
+ }
80
+ async function removeSavedItemData(itemType, itemId, client) {
81
+ const { error } = await client.DELETE('/userCollectionSaveForLaters/{id}/relationships/items', {
82
+ params: { path: { id: 'me' } },
83
+ body: {
84
+ data: [{ id: itemId, type: itemType }],
85
+ },
86
+ });
87
+ if (error) {
88
+ throw new Error(`Failed to remove saved item — ${JSON.stringify(error)}`);
89
+ }
90
+ return { id: itemId, type: itemType, removed: true };
91
+ }
92
+ async function removeSavedItem(itemType, itemId, json) {
93
+ const client = await (0, auth_1.getApiClient)();
94
+ try {
95
+ const result = await removeSavedItemData(itemType, itemId, client);
96
+ if (json) {
97
+ console.log(JSON.stringify(result, null, 2));
98
+ return;
99
+ }
100
+ console.log(`\n${itemType} ${itemId} removed from saved.`);
101
+ }
102
+ catch (err) {
103
+ console.error(`Error: ${err.message}`);
104
+ process.exit(1);
105
+ }
106
+ }
107
+ //# sourceMappingURL=saved.js.map
@@ -0,0 +1,13 @@
1
+ import type { SearchHistoryEntry } from './types';
2
+ export type { SearchHistoryEntry };
3
+ export declare function listSearchHistoryData(client: any, countryCode: string): Promise<SearchHistoryEntry[]>;
4
+ export declare function listSearchHistory(json: boolean): Promise<void>;
5
+ export declare function deleteSearchHistoryEntryData(entryId: string, client: any): Promise<{
6
+ id: string;
7
+ deleted: boolean;
8
+ }>;
9
+ export declare function deleteSearchHistoryEntry(entryId: string, json: boolean): Promise<void>;
10
+ export declare function clearSearchHistoryData(client: any, countryCode: string): Promise<{
11
+ deleted: number;
12
+ }>;
13
+ export declare function clearSearchHistory(json: boolean): Promise<void>;
@@ -0,0 +1,94 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.listSearchHistoryData = listSearchHistoryData;
4
+ exports.listSearchHistory = listSearchHistory;
5
+ exports.deleteSearchHistoryEntryData = deleteSearchHistoryEntryData;
6
+ exports.deleteSearchHistoryEntry = deleteSearchHistoryEntry;
7
+ exports.clearSearchHistoryData = clearSearchHistoryData;
8
+ exports.clearSearchHistory = clearSearchHistory;
9
+ const auth_1 = require("./auth");
10
+ async function listSearchHistoryData(client, countryCode) {
11
+ const { data, error } = await client.GET('/searchHistoryEntries', {
12
+ params: {
13
+ query: { countryCode },
14
+ },
15
+ });
16
+ if (error || !data) {
17
+ throw new Error(`Failed to list search history — ${JSON.stringify(error)}`);
18
+ }
19
+ const items = data.data ?? [];
20
+ return items.map((item) => ({
21
+ id: item.id,
22
+ query: item.attributes?.query ?? '',
23
+ }));
24
+ }
25
+ async function listSearchHistory(json) {
26
+ const client = await (0, auth_1.getApiClient)();
27
+ const countryCode = await (0, auth_1.getCountryCode)();
28
+ try {
29
+ const entries = await listSearchHistoryData(client, countryCode);
30
+ if (json) {
31
+ console.log(JSON.stringify(entries, null, 2));
32
+ return;
33
+ }
34
+ if (entries.length === 0) {
35
+ console.log('No search history found.');
36
+ return;
37
+ }
38
+ console.log('\nYour search history:\n');
39
+ for (const entry of entries) {
40
+ console.log(` [${entry.id}] ${entry.query}`);
41
+ }
42
+ console.log();
43
+ }
44
+ catch (err) {
45
+ console.error(`Error: ${err.message}`);
46
+ process.exit(1);
47
+ }
48
+ }
49
+ async function deleteSearchHistoryEntryData(entryId, client) {
50
+ const { error } = await client.DELETE('/searchHistoryEntries/{id}', {
51
+ params: { path: { id: entryId } },
52
+ });
53
+ if (error) {
54
+ throw new Error(`Failed to delete search history entry — ${JSON.stringify(error)}`);
55
+ }
56
+ return { id: entryId, deleted: true };
57
+ }
58
+ async function deleteSearchHistoryEntry(entryId, json) {
59
+ const client = await (0, auth_1.getApiClient)();
60
+ try {
61
+ const result = await deleteSearchHistoryEntryData(entryId, client);
62
+ if (json) {
63
+ console.log(JSON.stringify(result, null, 2));
64
+ return;
65
+ }
66
+ console.log(`\nSearch history entry ${entryId} deleted.`);
67
+ }
68
+ catch (err) {
69
+ console.error(`Error: ${err.message}`);
70
+ process.exit(1);
71
+ }
72
+ }
73
+ async function clearSearchHistoryData(client, countryCode) {
74
+ const entries = await listSearchHistoryData(client, countryCode);
75
+ await Promise.all(entries.map((e) => deleteSearchHistoryEntryData(e.id, client)));
76
+ return { deleted: entries.length };
77
+ }
78
+ async function clearSearchHistory(json) {
79
+ const client = await (0, auth_1.getApiClient)();
80
+ const countryCode = await (0, auth_1.getCountryCode)();
81
+ try {
82
+ const result = await clearSearchHistoryData(client, countryCode);
83
+ if (json) {
84
+ console.log(JSON.stringify(result, null, 2));
85
+ return;
86
+ }
87
+ console.log(`\nCleared ${result.deleted} search history entries.`);
88
+ }
89
+ catch (err) {
90
+ console.error(`Error: ${err.message}`);
91
+ process.exit(1);
92
+ }
93
+ }
94
+ //# sourceMappingURL=search-history.js.map
package/dist/search.d.ts CHANGED
@@ -1,4 +1,6 @@
1
- type SearchType = 'artist' | 'album' | 'track' | 'video' | 'playlist';
1
+ import type { SearchType, SearchResult, SearchSuggestionsResult } from './types';
2
+ export type { SearchType, SearchResult, SearchSuggestionsResult };
3
+ export declare function searchData(type: SearchType, query: string, client: any, countryCode: string): Promise<SearchResult[]>;
2
4
  export declare function search(type: SearchType, query: string, json: boolean): Promise<void>;
5
+ export declare function searchSuggestionsData(query: string, client: any, countryCode: string): Promise<SearchSuggestionsResult>;
3
6
  export declare function searchSuggestions(query: string, json: boolean): Promise<void>;
4
- export {};
package/dist/search.js CHANGED
@@ -1,6 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.searchData = searchData;
3
4
  exports.search = search;
5
+ exports.searchSuggestionsData = searchSuggestionsData;
4
6
  exports.searchSuggestions = searchSuggestions;
5
7
  const auth_1 = require("./auth");
6
8
  function formatDuration(isoDuration) {
@@ -15,8 +17,7 @@ function formatDuration(isoDuration) {
15
17
  const s = (match[3] ?? '0').padStart(2, '0');
16
18
  return `${h}${m}:${s}`;
17
19
  }
18
- async function search(type, query, json) {
19
- const client = await (0, auth_1.getApiClient)();
20
+ async function searchData(type, query, client, countryCode) {
20
21
  const includeMap = {
21
22
  artist: 'artists',
22
23
  album: 'albums',
@@ -28,14 +29,13 @@ async function search(type, query, json) {
28
29
  params: {
29
30
  path: { id: query },
30
31
  query: {
31
- countryCode: await (0, auth_1.getCountryCode)(),
32
+ countryCode,
32
33
  include: [includeMap[type]],
33
34
  },
34
35
  },
35
36
  });
36
37
  if (error || !data) {
37
- console.error(`Error: Search failed — ${JSON.stringify(error)}`);
38
- process.exit(1);
38
+ throw new Error(`Search failed — ${JSON.stringify(error)}`);
39
39
  }
40
40
  const included = data.included ?? [];
41
41
  const results = [];
@@ -111,41 +111,51 @@ async function search(type, query, json) {
111
111
  return pb - pa;
112
112
  });
113
113
  }
114
- if (json) {
115
- console.log(JSON.stringify(results, null, 2));
116
- return;
117
- }
118
- if (results.length === 0) {
119
- console.log(`No ${type}s found for "${query}".`);
120
- return;
114
+ return results;
115
+ }
116
+ async function search(type, query, json) {
117
+ const client = await (0, auth_1.getApiClient)();
118
+ const countryCode = await (0, auth_1.getCountryCode)();
119
+ try {
120
+ const results = await searchData(type, query, client, countryCode);
121
+ if (json) {
122
+ console.log(JSON.stringify(results, null, 2));
123
+ return;
124
+ }
125
+ if (results.length === 0) {
126
+ console.log(`No ${type}s found for "${query}".`);
127
+ return;
128
+ }
129
+ const typeLabel = type === 'playlist' ? 'playlists' : `${type}s`;
130
+ console.log(`\nSearch results for "${query}" (${typeLabel}):\n`);
131
+ for (const r of results) {
132
+ const extras = r.extra
133
+ ? Object.entries(r.extra)
134
+ .filter(([, v]) => v !== undefined && v !== null)
135
+ .map(([k, v]) => `${k}: ${v}`)
136
+ .join(', ')
137
+ : '';
138
+ console.log(` [${r.id}] ${r.name}${extras ? ` (${extras})` : ''}`);
139
+ }
140
+ console.log();
121
141
  }
122
- const typeLabel = type === 'playlist' ? 'playlists' : `${type}s`;
123
- console.log(`\nSearch results for "${query}" (${typeLabel}):\n`);
124
- for (const r of results) {
125
- const extras = r.extra
126
- ? Object.entries(r.extra)
127
- .filter(([, v]) => v !== undefined && v !== null)
128
- .map(([k, v]) => `${k}: ${v}`)
129
- .join(', ')
130
- : '';
131
- console.log(` [${r.id}] ${r.name}${extras ? ` (${extras})` : ''}`);
142
+ catch (err) {
143
+ console.error(`Error: ${err.message}`);
144
+ process.exit(1);
132
145
  }
133
- console.log();
134
146
  }
135
- async function searchSuggestions(query, json) {
136
- const client = await (0, auth_1.getApiClient)();
147
+ async function searchSuggestionsData(query, client, countryCode) {
137
148
  const { data, error } = await client.GET('/searchSuggestions/{id}', {
138
149
  params: {
139
150
  path: { id: query },
140
151
  query: {
141
- countryCode: await (0, auth_1.getCountryCode)(),
152
+ countryCode,
142
153
  include: ['directHits'],
143
154
  },
144
155
  },
145
156
  });
146
157
  if (error || !data) {
147
- console.error(`Error: Failed to get search suggestions — ${JSON.stringify(error)}`);
148
- process.exit(1);
158
+ throw new Error(`Failed to get search suggestions — ${JSON.stringify(error)}`);
149
159
  }
150
160
  const attrs = data.data?.attributes ?? {};
151
161
  const suggestions = (attrs.suggestions ?? []).map((s) => s.query ?? s);
@@ -158,26 +168,37 @@ async function searchSuggestions(query, json) {
158
168
  name: itemAttrs?.title ?? itemAttrs?.name ?? 'Unknown',
159
169
  };
160
170
  });
161
- const result = { suggestions, directHits };
162
- if (json) {
163
- console.log(JSON.stringify(result, null, 2));
164
- return;
165
- }
166
- if (suggestions.length > 0) {
167
- console.log(`\nSuggestions for "${query}":\n`);
168
- for (const s of suggestions) {
169
- console.log(` ${s}`);
171
+ return { suggestions, directHits };
172
+ }
173
+ async function searchSuggestions(query, json) {
174
+ const client = await (0, auth_1.getApiClient)();
175
+ const countryCode = await (0, auth_1.getCountryCode)();
176
+ try {
177
+ const result = await searchSuggestionsData(query, client, countryCode);
178
+ if (json) {
179
+ console.log(JSON.stringify(result, null, 2));
180
+ return;
170
181
  }
171
- }
172
- if (directHits.length > 0) {
173
- console.log(`\nDirect hits:\n`);
174
- for (const h of directHits) {
175
- console.log(` [${h.id}] (${h.type}) ${h.name}`);
182
+ if (result.suggestions.length > 0) {
183
+ console.log(`\nSuggestions for "${query}":\n`);
184
+ for (const s of result.suggestions) {
185
+ console.log(` ${s}`);
186
+ }
187
+ }
188
+ if (result.directHits.length > 0) {
189
+ console.log(`\nDirect hits:\n`);
190
+ for (const h of result.directHits) {
191
+ console.log(` [${h.id}] (${h.type}) ${h.name}`);
192
+ }
176
193
  }
194
+ if (result.suggestions.length === 0 && result.directHits.length === 0) {
195
+ console.log(`No suggestions found for "${query}".`);
196
+ }
197
+ console.log();
177
198
  }
178
- if (suggestions.length === 0 && directHits.length === 0) {
179
- console.log(`No suggestions found for "${query}".`);
199
+ catch (err) {
200
+ console.error(`Error: ${err.message}`);
201
+ process.exit(1);
180
202
  }
181
- console.log();
182
203
  }
183
204
  //# sourceMappingURL=search.js.map
package/dist/session.js CHANGED
@@ -42,15 +42,20 @@ const os = __importStar(require("os"));
42
42
  const SESSION_DIR = path.join(os.homedir(), '.tidal-cli');
43
43
  const SESSION_FILE = path.join(SESSION_DIR, 'session.json');
44
44
  function ensureDir() {
45
- if (!fs.existsSync(SESSION_DIR)) {
46
- fs.mkdirSync(SESSION_DIR, { mode: 0o700 });
45
+ try {
46
+ if (!fs.existsSync(SESSION_DIR)) {
47
+ fs.mkdirSync(SESSION_DIR, { mode: 0o700 });
48
+ }
49
+ }
50
+ catch {
51
+ // Serverless / read-only filesystem — skip
47
52
  }
48
53
  }
49
54
  function loadStorage() {
50
- ensureDir();
51
- if (!fs.existsSync(SESSION_FILE))
52
- return {};
53
55
  try {
56
+ ensureDir();
57
+ if (!fs.existsSync(SESSION_FILE))
58
+ return {};
54
59
  return JSON.parse(fs.readFileSync(SESSION_FILE, 'utf-8'));
55
60
  }
56
61
  catch {
@@ -58,8 +63,13 @@ function loadStorage() {
58
63
  }
59
64
  }
60
65
  function saveStorage(data) {
61
- ensureDir();
62
- fs.writeFileSync(SESSION_FILE, JSON.stringify(data, null, 2), { mode: 0o600 });
66
+ try {
67
+ ensureDir();
68
+ fs.writeFileSync(SESSION_FILE, JSON.stringify(data, null, 2), { mode: 0o600 });
69
+ }
70
+ catch {
71
+ // Serverless / read-only filesystem — skip
72
+ }
63
73
  }
64
74
  /**
65
75
  * Install a globalThis.localStorage polyfill backed by ~/.tidal-cli/session.json.
@@ -0,0 +1,5 @@
1
+ import type { ShareLink } from './types';
2
+ export type { ShareLink };
3
+ export type ShareableType = 'tracks' | 'albums';
4
+ export declare function createShareData(resourceType: ShareableType, resourceId: string, client: any): Promise<ShareLink>;
5
+ export declare function createShare(resourceType: ShareableType, resourceId: string, json: boolean): Promise<void>;
package/dist/share.js ADDED
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createShareData = createShareData;
4
+ exports.createShare = createShare;
5
+ const auth_1 = require("./auth");
6
+ async function createShareData(resourceType, resourceId, client) {
7
+ const { data, error } = await client.POST('/shares', {
8
+ body: {
9
+ data: {
10
+ type: 'shares',
11
+ relationships: {
12
+ sharedResources: {
13
+ data: [{ id: resourceId, type: resourceType }],
14
+ },
15
+ },
16
+ },
17
+ },
18
+ });
19
+ if (error || !data) {
20
+ throw new Error(`Failed to create share — ${JSON.stringify(error)}`);
21
+ }
22
+ const created = data.data ?? {};
23
+ const attrs = created.attributes ?? {};
24
+ const tidalLink = (attrs.externalLinks ?? []).find((l) => /tidal\.com/i.test(l?.href ?? ''));
25
+ return {
26
+ id: created.id,
27
+ code: attrs.code ?? '',
28
+ createdAt: attrs.createdAt,
29
+ url: tidalLink?.href ?? attrs.externalLinks?.[0]?.href,
30
+ };
31
+ }
32
+ async function createShare(resourceType, resourceId, json) {
33
+ const client = await (0, auth_1.getApiClient)();
34
+ try {
35
+ const result = await createShareData(resourceType, resourceId, client);
36
+ if (json) {
37
+ console.log(JSON.stringify(result, null, 2));
38
+ return;
39
+ }
40
+ console.log(`\nShare created for ${resourceType} ${resourceId}:`);
41
+ console.log(` Code: ${result.code}`);
42
+ if (result.url)
43
+ console.log(` URL: ${result.url}`);
44
+ if (result.createdAt)
45
+ console.log(` Created: ${result.createdAt}`);
46
+ console.log();
47
+ }
48
+ catch (err) {
49
+ console.error(`Error: ${err.message}`);
50
+ process.exit(1);
51
+ }
52
+ }
53
+ //# sourceMappingURL=share.js.map
package/dist/track.d.ts CHANGED
@@ -1,4 +1,18 @@
1
+ import type { TrackInfo, SimilarTrack, RadioPlaylist } from './types';
2
+ export type { TrackInfo, SimilarTrack };
3
+ export declare function getTrackInfoData(trackId: string, client: any, countryCode: string): Promise<TrackInfo>;
1
4
  export declare function getTrackInfo(trackId: string, json: boolean): Promise<void>;
5
+ export declare function getTrackRadioData(trackId: string, client: any, countryCode: string): Promise<RadioPlaylist[]>;
2
6
  export declare function getTrackRadio(trackId: string, json: boolean): Promise<void>;
7
+ interface TrackByIsrcResult {
8
+ id: string;
9
+ title: string;
10
+ artists: string[];
11
+ duration: string;
12
+ isrc?: string;
13
+ popularity?: number;
14
+ }
15
+ export declare function getTrackByIsrcData(isrc: string, client: any, countryCode: string): Promise<TrackByIsrcResult[]>;
3
16
  export declare function getTrackByIsrc(isrc: string, json: boolean): Promise<void>;
17
+ export declare function getSimilarTracksData(trackId: string, client: any, countryCode: string): Promise<SimilarTrack[]>;
4
18
  export declare function getSimilarTracks(trackId: string, json: boolean): Promise<void>;