@involvex/youtube-music-cli 0.0.46 → 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 (118) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/dist/cli.js.map +1004 -0
  3. package/dist/source/hooks/usePlayer.d.ts +1 -0
  4. package/dist/source/services/player-state/player-state.service.d.ts +1 -0
  5. package/dist/source/stores/player.store.d.ts +1 -0
  6. package/dist/source/types/actions.d.ts +4 -0
  7. package/dist/source/types/player.types.d.ts +3 -2
  8. package/dist/source/utils/constants.d.ts +1 -0
  9. package/dist/source/utils/icons.d.ts +1 -0
  10. package/dist/youtube-music-cli +0 -0
  11. package/package.json +1 -1
  12. package/dist/eslint.config.js +0 -55
  13. package/dist/package.json +0 -120
  14. package/dist/scripts/build-cli.js +0 -46
  15. package/dist/source/app.js +0 -17
  16. package/dist/source/cli.js +0 -504
  17. package/dist/source/components/common/ErrorBoundary.js +0 -22
  18. package/dist/source/components/common/Help.js +0 -18
  19. package/dist/source/components/common/ShortcutsBar.js +0 -80
  20. package/dist/source/components/config/ConfigLayout.js +0 -84
  21. package/dist/source/components/config/KeybindingsLayout.js +0 -107
  22. package/dist/source/components/export/ExportLayout.js +0 -111
  23. package/dist/source/components/import/ImportLayout.js +0 -119
  24. package/dist/source/components/import/ImportProgress.js +0 -73
  25. package/dist/source/components/layouts/ExploreLayout.js +0 -72
  26. package/dist/source/components/layouts/HistoryLayout.js +0 -37
  27. package/dist/source/components/layouts/LyricsLayout.js +0 -89
  28. package/dist/source/components/layouts/MainLayout.js +0 -190
  29. package/dist/source/components/layouts/MiniPlayerLayout.js +0 -20
  30. package/dist/source/components/layouts/PlayerLayout.js +0 -9
  31. package/dist/source/components/layouts/PluginsLayout.js +0 -77
  32. package/dist/source/components/layouts/SearchLayout.js +0 -193
  33. package/dist/source/components/layouts/TrendingLayout.js +0 -59
  34. package/dist/source/components/player/NowPlaying.js +0 -45
  35. package/dist/source/components/player/PlayerControls.js +0 -83
  36. package/dist/source/components/player/ProgressBar.js +0 -19
  37. package/dist/source/components/player/QueueList.js +0 -36
  38. package/dist/source/components/player/Suggestions.js +0 -50
  39. package/dist/source/components/playlist/PlaylistList.js +0 -138
  40. package/dist/source/components/plugins/PluginInstallDialog.js +0 -41
  41. package/dist/source/components/plugins/PluginsAvailable.js +0 -55
  42. package/dist/source/components/plugins/PluginsList.js +0 -18
  43. package/dist/source/components/search/SearchBar.js +0 -55
  44. package/dist/source/components/search/SearchHistory.js +0 -35
  45. package/dist/source/components/search/SearchResults.js +0 -280
  46. package/dist/source/components/settings/Settings.js +0 -211
  47. package/dist/source/components/theme/ThemeSwitcher.js +0 -11
  48. package/dist/source/config/themes.config.js +0 -123
  49. package/dist/source/contexts/theme.context.js +0 -29
  50. package/dist/source/hooks/useKeyboard.js +0 -188
  51. package/dist/source/hooks/useKeyboardBlocker.js +0 -45
  52. package/dist/source/hooks/useNavigation.js +0 -5
  53. package/dist/source/hooks/usePlayer.js +0 -43
  54. package/dist/source/hooks/usePlaylist.js +0 -65
  55. package/dist/source/hooks/useSearch.js +0 -76
  56. package/dist/source/hooks/useSleepTimer.js +0 -48
  57. package/dist/source/hooks/useTerminalSize.js +0 -24
  58. package/dist/source/hooks/useTheme.js +0 -5
  59. package/dist/source/hooks/useYouTubeMusic.js +0 -112
  60. package/dist/source/main.js +0 -127
  61. package/dist/source/services/cache/cache.service.js +0 -67
  62. package/dist/source/services/completions/completions.service.js +0 -313
  63. package/dist/source/services/config/config.service.js +0 -191
  64. package/dist/source/services/discord/discord-rpc.service.js +0 -95
  65. package/dist/source/services/download/download.service.js +0 -350
  66. package/dist/source/services/export/export.service.js +0 -131
  67. package/dist/source/services/history/history.service.js +0 -83
  68. package/dist/source/services/import/import.service.js +0 -272
  69. package/dist/source/services/import/spotify.service.js +0 -171
  70. package/dist/source/services/import/track-matcher.service.js +0 -271
  71. package/dist/source/services/import/youtube-import.service.js +0 -84
  72. package/dist/source/services/logger/logger.service.js +0 -52
  73. package/dist/source/services/lyrics/lyrics.service.js +0 -93
  74. package/dist/source/services/mpris/mpris.service.js +0 -78
  75. package/dist/source/services/notification/notification.service.js +0 -57
  76. package/dist/source/services/player/dependency-check.service.js +0 -140
  77. package/dist/source/services/player/player.service.js +0 -478
  78. package/dist/source/services/player-state/player-state.service.js +0 -122
  79. package/dist/source/services/plugin/plugin-audio-api.js +0 -36
  80. package/dist/source/services/plugin/plugin-context.js +0 -256
  81. package/dist/source/services/plugin/plugin-hooks.service.js +0 -135
  82. package/dist/source/services/plugin/plugin-installer.service.js +0 -248
  83. package/dist/source/services/plugin/plugin-loader.service.js +0 -161
  84. package/dist/source/services/plugin/plugin-permissions.service.js +0 -194
  85. package/dist/source/services/plugin/plugin-registry.service.js +0 -215
  86. package/dist/source/services/plugin/plugin-ui-api.js +0 -46
  87. package/dist/source/services/plugin/plugin-updater.service.js +0 -206
  88. package/dist/source/services/scrobbling/scrobbling.service.js +0 -115
  89. package/dist/source/services/sleep-timer/sleep-timer.service.js +0 -45
  90. package/dist/source/services/version-check/version-check.service.js +0 -121
  91. package/dist/source/services/web/static-file.service.js +0 -185
  92. package/dist/source/services/web/web-server-manager.js +0 -506
  93. package/dist/source/services/web/web-streaming.service.js +0 -290
  94. package/dist/source/services/web/websocket.server.js +0 -267
  95. package/dist/source/services/youtube-music/api.js +0 -649
  96. package/dist/source/services/youtube-music/search.service.js +0 -38
  97. package/dist/source/stores/history.store.js +0 -64
  98. package/dist/source/stores/navigation.store.js +0 -90
  99. package/dist/source/stores/player.store.js +0 -724
  100. package/dist/source/stores/plugins.store.js +0 -177
  101. package/dist/source/types/actions.js +0 -1
  102. package/dist/source/types/cli.types.js +0 -1
  103. package/dist/source/types/config.types.js +0 -1
  104. package/dist/source/types/history.types.js +0 -1
  105. package/dist/source/types/import.types.js +0 -2
  106. package/dist/source/types/keyboard.types.js +0 -1
  107. package/dist/source/types/navigation.types.js +0 -1
  108. package/dist/source/types/player.types.js +0 -1
  109. package/dist/source/types/playlist.types.js +0 -1
  110. package/dist/source/types/plugin.types.js +0 -1
  111. package/dist/source/types/theme.types.js +0 -1
  112. package/dist/source/types/web.types.js +0 -2
  113. package/dist/source/types/youtube-music.types.js +0 -1
  114. package/dist/source/types/youtubei.types.js +0 -3
  115. package/dist/source/utils/constants.js +0 -134
  116. package/dist/source/utils/format.js +0 -24
  117. package/dist/source/utils/icons.js +0 -26
  118. 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
- }