@involvex/youtube-music-cli 0.0.47 → 0.0.48

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 (111) hide show
  1. package/CHANGELOG.md +2 -0
  2. package/dist/cli.js.map +3 -3
  3. package/dist/youtube-music-cli +0 -0
  4. package/package.json +1 -1
  5. package/dist/eslint.config.js +0 -55
  6. package/dist/package.json +0 -120
  7. package/dist/scripts/build-cli.js +0 -46
  8. package/dist/source/app.js +0 -17
  9. package/dist/source/cli.js +0 -504
  10. package/dist/source/components/common/ErrorBoundary.js +0 -22
  11. package/dist/source/components/common/Help.js +0 -18
  12. package/dist/source/components/common/ShortcutsBar.js +0 -89
  13. package/dist/source/components/config/ConfigLayout.js +0 -84
  14. package/dist/source/components/config/KeybindingsLayout.js +0 -107
  15. package/dist/source/components/export/ExportLayout.js +0 -111
  16. package/dist/source/components/import/ImportLayout.js +0 -119
  17. package/dist/source/components/import/ImportProgress.js +0 -73
  18. package/dist/source/components/layouts/ExploreLayout.js +0 -72
  19. package/dist/source/components/layouts/HistoryLayout.js +0 -37
  20. package/dist/source/components/layouts/LyricsLayout.js +0 -89
  21. package/dist/source/components/layouts/MainLayout.js +0 -190
  22. package/dist/source/components/layouts/MiniPlayerLayout.js +0 -20
  23. package/dist/source/components/layouts/PlayerLayout.js +0 -9
  24. package/dist/source/components/layouts/PluginsLayout.js +0 -77
  25. package/dist/source/components/layouts/SearchLayout.js +0 -193
  26. package/dist/source/components/layouts/TrendingLayout.js +0 -59
  27. package/dist/source/components/player/NowPlaying.js +0 -45
  28. package/dist/source/components/player/PlayerControls.js +0 -83
  29. package/dist/source/components/player/ProgressBar.js +0 -19
  30. package/dist/source/components/player/QueueList.js +0 -36
  31. package/dist/source/components/player/Suggestions.js +0 -50
  32. package/dist/source/components/playlist/PlaylistList.js +0 -138
  33. package/dist/source/components/plugins/PluginInstallDialog.js +0 -41
  34. package/dist/source/components/plugins/PluginsAvailable.js +0 -55
  35. package/dist/source/components/plugins/PluginsList.js +0 -18
  36. package/dist/source/components/search/SearchBar.js +0 -55
  37. package/dist/source/components/search/SearchHistory.js +0 -35
  38. package/dist/source/components/search/SearchResults.js +0 -280
  39. package/dist/source/components/settings/Settings.js +0 -211
  40. package/dist/source/components/theme/ThemeSwitcher.js +0 -11
  41. package/dist/source/config/themes.config.js +0 -123
  42. package/dist/source/contexts/theme.context.js +0 -29
  43. package/dist/source/hooks/useKeyboard.js +0 -188
  44. package/dist/source/hooks/useKeyboardBlocker.js +0 -45
  45. package/dist/source/hooks/useNavigation.js +0 -5
  46. package/dist/source/hooks/usePlayer.js +0 -43
  47. package/dist/source/hooks/usePlaylist.js +0 -65
  48. package/dist/source/hooks/useSearch.js +0 -76
  49. package/dist/source/hooks/useSleepTimer.js +0 -48
  50. package/dist/source/hooks/useTerminalSize.js +0 -24
  51. package/dist/source/hooks/useTheme.js +0 -5
  52. package/dist/source/hooks/useYouTubeMusic.js +0 -112
  53. package/dist/source/main.js +0 -127
  54. package/dist/source/services/cache/cache.service.js +0 -67
  55. package/dist/source/services/completions/completions.service.js +0 -313
  56. package/dist/source/services/config/config.service.js +0 -191
  57. package/dist/source/services/discord/discord-rpc.service.js +0 -95
  58. package/dist/source/services/download/download.service.js +0 -350
  59. package/dist/source/services/export/export.service.js +0 -131
  60. package/dist/source/services/history/history.service.js +0 -83
  61. package/dist/source/services/import/import.service.js +0 -272
  62. package/dist/source/services/import/spotify.service.js +0 -171
  63. package/dist/source/services/import/track-matcher.service.js +0 -271
  64. package/dist/source/services/import/youtube-import.service.js +0 -84
  65. package/dist/source/services/logger/logger.service.js +0 -52
  66. package/dist/source/services/lyrics/lyrics.service.js +0 -93
  67. package/dist/source/services/mpris/mpris.service.js +0 -78
  68. package/dist/source/services/notification/notification.service.js +0 -57
  69. package/dist/source/services/player/dependency-check.service.js +0 -140
  70. package/dist/source/services/player/player.service.js +0 -478
  71. package/dist/source/services/player-state/player-state.service.js +0 -123
  72. package/dist/source/services/plugin/plugin-audio-api.js +0 -36
  73. package/dist/source/services/plugin/plugin-context.js +0 -256
  74. package/dist/source/services/plugin/plugin-hooks.service.js +0 -135
  75. package/dist/source/services/plugin/plugin-installer.service.js +0 -248
  76. package/dist/source/services/plugin/plugin-loader.service.js +0 -161
  77. package/dist/source/services/plugin/plugin-permissions.service.js +0 -194
  78. package/dist/source/services/plugin/plugin-registry.service.js +0 -215
  79. package/dist/source/services/plugin/plugin-ui-api.js +0 -46
  80. package/dist/source/services/plugin/plugin-updater.service.js +0 -206
  81. package/dist/source/services/scrobbling/scrobbling.service.js +0 -115
  82. package/dist/source/services/sleep-timer/sleep-timer.service.js +0 -45
  83. package/dist/source/services/version-check/version-check.service.js +0 -121
  84. package/dist/source/services/web/static-file.service.js +0 -185
  85. package/dist/source/services/web/web-server-manager.js +0 -507
  86. package/dist/source/services/web/web-streaming.service.js +0 -292
  87. package/dist/source/services/web/websocket.server.js +0 -267
  88. package/dist/source/services/youtube-music/api.js +0 -649
  89. package/dist/source/services/youtube-music/search.service.js +0 -38
  90. package/dist/source/stores/history.store.js +0 -64
  91. package/dist/source/stores/navigation.store.js +0 -90
  92. package/dist/source/stores/player.store.js +0 -789
  93. package/dist/source/stores/plugins.store.js +0 -177
  94. package/dist/source/types/actions.js +0 -1
  95. package/dist/source/types/cli.types.js +0 -1
  96. package/dist/source/types/config.types.js +0 -1
  97. package/dist/source/types/history.types.js +0 -1
  98. package/dist/source/types/import.types.js +0 -2
  99. package/dist/source/types/keyboard.types.js +0 -1
  100. package/dist/source/types/navigation.types.js +0 -1
  101. package/dist/source/types/player.types.js +0 -1
  102. package/dist/source/types/playlist.types.js +0 -1
  103. package/dist/source/types/plugin.types.js +0 -1
  104. package/dist/source/types/theme.types.js +0 -1
  105. package/dist/source/types/web.types.js +0 -2
  106. package/dist/source/types/youtube-music.types.js +0 -1
  107. package/dist/source/types/youtubei.types.js +0 -3
  108. package/dist/source/utils/constants.js +0 -135
  109. package/dist/source/utils/format.js +0 -24
  110. package/dist/source/utils/icons.js +0 -28
  111. package/dist/source/utils/search-filters.js +0 -100
@@ -1,272 +0,0 @@
1
- import { getYouTubeImportService } from "./youtube-import.service.js";
2
- import { getSpotifyImportService } from "./spotify.service.js";
3
- import { getTrackMatcherService } from "./track-matcher.service.js";
4
- import { getConfigService } from "../config/config.service.js";
5
- import { logger } from "../logger/logger.service.js";
6
- // Helper to get track name from either Spotify or YouTube track
7
- function getTrackName(track) {
8
- return track.name || track.title;
9
- }
10
- class ImportService {
11
- progressCallbacks = new Set();
12
- currentImport = null;
13
- /**
14
- * Subscribe to import progress updates
15
- */
16
- onProgress(callback) {
17
- this.progressCallbacks.add(callback);
18
- return () => {
19
- this.progressCallbacks.delete(callback);
20
- };
21
- }
22
- /**
23
- * Emit progress to all subscribers
24
- */
25
- emitProgress(progress) {
26
- for (const callback of this.progressCallbacks) {
27
- callback(progress);
28
- }
29
- }
30
- /**
31
- * Create a unique playlist ID
32
- */
33
- generatePlaylistId() {
34
- return `import_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
35
- }
36
- /**
37
- * Save imported playlist to config
38
- */
39
- savePlaylist(name, tracks) {
40
- const configService = getConfigService();
41
- const playlist = {
42
- playlistId: this.generatePlaylistId(),
43
- name,
44
- tracks,
45
- };
46
- // Get existing playlists and add the new one
47
- const existingPlaylists = configService.get('playlists') ?? [];
48
- const updatedPlaylists = [...existingPlaylists, playlist];
49
- configService.set('playlists', updatedPlaylists);
50
- logger.info('ImportService', 'Playlist saved to config', {
51
- playlistId: playlist.playlistId,
52
- trackCount: tracks.length,
53
- });
54
- return playlist.playlistId;
55
- }
56
- /**
57
- * Import a playlist from Spotify or YouTube
58
- */
59
- async importPlaylist(source, urlOrId, customName, signal) {
60
- const startTime = Date.now();
61
- this.currentImport = { source, url: urlOrId, startTime };
62
- // Initial progress
63
- this.emitProgress({
64
- status: 'fetching',
65
- current: 0,
66
- total: 0,
67
- message: `Fetching ${source} playlist...`,
68
- });
69
- let originalTracks = [];
70
- let playlistName = customName ?? `Imported ${source} playlist`;
71
- try {
72
- // Step 1: Fetch playlist
73
- if (source === 'youtube') {
74
- const youtubeService = getYouTubeImportService();
75
- const playlist = await youtubeService.fetchPlaylist(urlOrId);
76
- if (!playlist) {
77
- throw new Error('Failed to fetch YouTube playlist. Please check the URL/ID.');
78
- }
79
- originalTracks = playlist.tracks;
80
- playlistName = customName ?? playlist.name;
81
- }
82
- else {
83
- const spotifyService = getSpotifyImportService();
84
- const playlist = await spotifyService.fetchPlaylist(urlOrId);
85
- if (!playlist) {
86
- throw new Error('Failed to fetch Spotify playlist. It may be private or invalid.');
87
- }
88
- if (playlist.tracks.length === 0) {
89
- throw new Error('No tracks found. The playlist may be private or require authentication.');
90
- }
91
- originalTracks = playlist.tracks;
92
- playlistName = customName ?? playlist.name;
93
- }
94
- const total = originalTracks.length;
95
- // Check for abort
96
- if (signal?.aborted) {
97
- this.emitProgress({
98
- status: 'cancelled',
99
- current: 0,
100
- total,
101
- message: 'Import cancelled',
102
- });
103
- throw new Error('Import cancelled');
104
- }
105
- // Step 2: Match tracks
106
- this.emitProgress({
107
- status: 'matching',
108
- current: 0,
109
- total,
110
- message: 'Matching tracks...',
111
- });
112
- const trackMatcher = getTrackMatcherService();
113
- const matchedTracks = [];
114
- const errors = [];
115
- let matched = 0;
116
- let failed = 0;
117
- for (let i = 0; i < originalTracks.length; i++) {
118
- // Check for abort
119
- if (signal?.aborted) {
120
- this.emitProgress({
121
- status: 'cancelled',
122
- current: i,
123
- total,
124
- currentTrack: originalTracks[i]
125
- ? getTrackName(originalTracks[i])
126
- : undefined,
127
- message: 'Import cancelled',
128
- });
129
- throw new Error('Import cancelled');
130
- }
131
- const originalTrack = originalTracks[i];
132
- const trackName = getTrackName(originalTrack);
133
- this.emitProgress({
134
- status: 'matching',
135
- current: i,
136
- total,
137
- currentTrack: trackName,
138
- message: `Matching "${trackName}"...`,
139
- });
140
- const match = await trackMatcher.findMatch(originalTrack);
141
- if (match.matchedTrack) {
142
- matchedTracks.push(match.matchedTrack);
143
- matched++;
144
- }
145
- else {
146
- failed++;
147
- const errorMsg = match.error
148
- ? `${trackName}: ${match.error}`
149
- : `No match found for "${trackName}"`;
150
- errors.push(errorMsg);
151
- }
152
- // Throttle progress updates (every 5 tracks or at the end)
153
- if (i % 5 === 0 || i === total - 1) {
154
- this.emitProgress({
155
- status: 'matching',
156
- current: i + 1,
157
- total,
158
- currentTrack: trackName,
159
- message: `Matched ${matched}/${total} tracks`,
160
- });
161
- }
162
- }
163
- // Step 3: Create playlist
164
- this.emitProgress({
165
- status: 'creating',
166
- current: total,
167
- total,
168
- message: 'Creating playlist...',
169
- });
170
- const playlistId = this.savePlaylist(playlistName, matchedTracks);
171
- // Final progress
172
- this.emitProgress({
173
- status: 'completed',
174
- current: total,
175
- total,
176
- message: `Import completed: ${matched} tracks matched`,
177
- });
178
- const result = {
179
- playlistId,
180
- playlistName,
181
- source,
182
- total,
183
- matched,
184
- failed,
185
- matches: [], // Could be populated if needed for detailed results
186
- errors,
187
- duration: Date.now() - startTime,
188
- };
189
- logger.info('ImportService', 'Import completed', {
190
- playlistId,
191
- playlistName,
192
- source,
193
- total,
194
- matched,
195
- failed,
196
- duration: result.duration,
197
- });
198
- return result;
199
- }
200
- catch (error) {
201
- const message = error instanceof Error ? error.message : String(error);
202
- logger.error('ImportService', 'Import failed', {
203
- source,
204
- url: urlOrId,
205
- error: message,
206
- });
207
- if (message === 'Import cancelled') {
208
- throw error;
209
- }
210
- this.emitProgress({
211
- status: 'failed',
212
- current: 0,
213
- total: originalTracks.length,
214
- message: `Import failed: ${message}`,
215
- });
216
- throw error;
217
- }
218
- finally {
219
- this.currentImport = null;
220
- }
221
- }
222
- /**
223
- * Validate a playlist URL/ID before importing
224
- */
225
- async validatePlaylist(source, urlOrId) {
226
- try {
227
- if (source === 'youtube') {
228
- const service = getYouTubeImportService();
229
- return await service.validatePlaylist(urlOrId);
230
- }
231
- else {
232
- const service = getSpotifyImportService();
233
- return await service.validatePlaylist(urlOrId);
234
- }
235
- }
236
- catch {
237
- return false;
238
- }
239
- }
240
- /**
241
- * Get current import status
242
- */
243
- getCurrentImport() {
244
- if (!this.currentImport)
245
- return null;
246
- return {
247
- source: this.currentImport.source,
248
- url: this.currentImport.url,
249
- elapsed: Date.now() - this.currentImport.startTime,
250
- };
251
- }
252
- /**
253
- * Cancel the current import
254
- */
255
- cancelImport() {
256
- this.currentImport = null;
257
- this.emitProgress({
258
- status: 'cancelled',
259
- current: 0,
260
- total: 0,
261
- message: 'Import cancelled',
262
- });
263
- }
264
- }
265
- // Singleton instance
266
- let importServiceInstance = null;
267
- export function getImportService() {
268
- if (!importServiceInstance) {
269
- importServiceInstance = new ImportService();
270
- }
271
- return importServiceInstance;
272
- }
@@ -1,171 +0,0 @@
1
- import { logger } from "../logger/logger.service.js";
2
- class SpotifyImportService {
3
- OEMBED_URL = 'https://open.spotify.com/oembed';
4
- /**
5
- * Extract playlist ID from various Spotify URL formats
6
- */
7
- extractPlaylistId(input) {
8
- // Direct playlist ID (base62 string, typically 22 characters)
9
- if (/^[A-Za-z0-9]{22}$/.test(input)) {
10
- return input;
11
- }
12
- // spotify:playlist:ID format
13
- const uriMatch = input.match(/spotify:playlist:([A-Za-z0-9]+)/);
14
- if (uriMatch) {
15
- return uriMatch[1] ?? null;
16
- }
17
- // open.spotify.com/playlist/ID format
18
- const urlMatch = input.match(/open\.spotify\.com\/playlist\/([A-Za-z0-9]+)/);
19
- if (urlMatch) {
20
- return urlMatch[1] ?? null;
21
- }
22
- return null;
23
- }
24
- /**
25
- * Build a Spotify playlist URL from ID
26
- */
27
- buildPlaylistUrl(playlistId) {
28
- return `https://open.spotify.com/playlist/${playlistId}`;
29
- }
30
- /**
31
- * Fetch playlist metadata using Spotify oEmbed API
32
- * This works for public playlists without authentication
33
- */
34
- async fetchPlaylistMetadata(urlOrId) {
35
- const playlistId = this.extractPlaylistId(urlOrId);
36
- if (!playlistId) {
37
- return null;
38
- }
39
- const playlistUrl = this.buildPlaylistUrl(playlistId);
40
- const oembedUrl = `${this.OEMBED_URL}?url=${encodeURIComponent(playlistUrl)}`;
41
- try {
42
- logger.debug('SpotifyImportService', 'Fetching oEmbed metadata', {
43
- playlistId,
44
- });
45
- const response = await fetch(oembedUrl);
46
- if (!response.ok) {
47
- logger.warn('SpotifyImportService', 'oEmbed request failed', {
48
- status: response.status,
49
- playlistId,
50
- });
51
- return null;
52
- }
53
- const data = (await response.json());
54
- return {
55
- title: data.title || 'Unknown Playlist',
56
- url: playlistUrl,
57
- };
58
- }
59
- catch (error) {
60
- logger.error('SpotifyImportService', 'oEmbed fetch failed', {
61
- playlistId,
62
- error: error instanceof Error ? error.message : String(error),
63
- });
64
- return null;
65
- }
66
- }
67
- /**
68
- * Fetch playlist tracks by scraping the Spotify Web API
69
- * Note: This method has limitations and may not work for private playlists
70
- */
71
- async fetchPlaylistTracks(playlistId) {
72
- const metadata = await this.fetchPlaylistMetadata(playlistId);
73
- if (!metadata) {
74
- return null;
75
- }
76
- try {
77
- // Use Spotify's Web API endpoint for playlist tracks (no auth required for public playlists)
78
- const apiUrl = `https://api.spotify.com/v1/playlists/${playlistId}/tracks?limit=100`;
79
- logger.debug('SpotifyImportService', 'Fetching tracks from API', {
80
- playlistId,
81
- });
82
- const response = await fetch(apiUrl, {
83
- headers: {
84
- Accept: 'application/json',
85
- },
86
- });
87
- // API may require auth for some playlists
88
- if (!response.ok) {
89
- if (response.status === 401 || response.status === 403) {
90
- logger.warn('SpotifyImportService', 'Playlist requires authentication', {
91
- playlistId,
92
- status: response.status,
93
- });
94
- return this.createPartialPlaylist(playlistId, metadata);
95
- }
96
- return null;
97
- }
98
- const data = (await response.json());
99
- const tracks = data.items
100
- ?.filter(item => item.track)
101
- .map(item => ({
102
- id: item.track.id ?? '',
103
- name: item.track.name ?? 'Unknown Track',
104
- artists: item.track.artists?.map(a => a.name ?? 'Unknown Artist') ?? [],
105
- album: item.track.album?.name,
106
- duration: Math.round((item.track.duration_ms ?? 0) / 1000),
107
- })) ?? [];
108
- return {
109
- id: playlistId,
110
- name: metadata.title,
111
- tracks,
112
- isPublic: true,
113
- url: metadata.url,
114
- };
115
- }
116
- catch (error) {
117
- logger.error('SpotifyImportService', 'Failed to fetch playlist', {
118
- playlistId,
119
- error: error instanceof Error ? error.message : String(error),
120
- });
121
- return null;
122
- }
123
- }
124
- /**
125
- * Create a partial playlist when only metadata is available
126
- * This is used when authentication is required but we have basic info
127
- */
128
- createPartialPlaylist(playlistId, metadata) {
129
- logger.info('SpotifyImportService', 'Creating partial playlist (auth required)', {
130
- playlistId,
131
- });
132
- return {
133
- id: playlistId,
134
- name: metadata.title,
135
- tracks: [],
136
- isPublic: false,
137
- url: metadata.url,
138
- };
139
- }
140
- /**
141
- * Fetch a Spotify playlist (with graceful degradation)
142
- */
143
- async fetchPlaylist(urlOrId) {
144
- const playlistId = this.extractPlaylistId(urlOrId);
145
- if (!playlistId) {
146
- logger.warn('SpotifyImportService', 'Invalid Spotify playlist URL or ID', {
147
- input: urlOrId,
148
- });
149
- return null;
150
- }
151
- logger.info('SpotifyImportService', 'Fetching Spotify playlist', {
152
- playlistId,
153
- });
154
- return this.fetchPlaylistTracks(playlistId);
155
- }
156
- /**
157
- * Validate if a playlist is accessible
158
- */
159
- async validatePlaylist(urlOrId) {
160
- const playlist = await this.fetchPlaylist(urlOrId);
161
- return playlist !== null;
162
- }
163
- }
164
- // Singleton instance
165
- let spotifyImportServiceInstance = null;
166
- export function getSpotifyImportService() {
167
- if (!spotifyImportServiceInstance) {
168
- spotifyImportServiceInstance = new SpotifyImportService();
169
- }
170
- return spotifyImportServiceInstance;
171
- }