@involvex/youtube-music-cli 0.0.47 → 0.0.49

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 +8 -0
  2. package/dist/cli.js.map +6 -6
  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,188 +0,0 @@
1
- // Keyboard input handling hook
2
- import { useEffect, useRef } from 'react';
3
- import { useInput } from 'ink';
4
- import { logger } from "../services/logger/logger.service.js";
5
- import { useKeyboardBlockContext } from "./useKeyboardBlocker.js";
6
- // Global registry for key handlers
7
- const registry = new Set();
8
- /**
9
- * Hook to bind keyboard shortcuts.
10
- * This uses a centralized manager to avoid multiple useInput calls and memory leaks.
11
- * Uses a ref-based approach to always call the latest handler without stale closures.
12
- */
13
- export function useKeyBinding(keys, handler, options) {
14
- const handlerRef = useRef(handler);
15
- handlerRef.current = handler;
16
- useEffect(() => {
17
- const entry = {
18
- keys,
19
- handler: () => handlerRef.current(),
20
- bypassBlock: options?.bypassBlock,
21
- };
22
- registry.add(entry);
23
- return () => {
24
- registry.delete(entry);
25
- };
26
- }, [keys, options?.bypassBlock]); // keys and bypassBlock are deps; handlerRef is a stable ref
27
- }
28
- /**
29
- * Global Keyboard Manager Component
30
- * This should be rendered once at the root of the app.
31
- */
32
- export function KeyboardManager() {
33
- const { blockCount } = useKeyboardBlockContext();
34
- useInput((input, key) => {
35
- if (blockCount > 0) {
36
- // When keyboard input is blocked (e.g., within a focused text input),
37
- // check if any entry has bypassBlock flag and matches this key.
38
- for (const entry of registry) {
39
- if (entry.bypassBlock) {
40
- for (const binding of entry.keys) {
41
- const lowerBinding = binding.toLowerCase();
42
- // Check for ESC key (most common bypass case)
43
- if (lowerBinding === 'escape' && key.escape) {
44
- entry.handler();
45
- return;
46
- }
47
- // Handle other bypass keys
48
- const isMatch = ((lowerBinding === 'return' || lowerBinding === 'enter') &&
49
- key.return) ||
50
- (lowerBinding === 'backspace' && key.backspace) ||
51
- (lowerBinding === 'tab' && key.tab) ||
52
- (lowerBinding === 'up' && key.upArrow) ||
53
- (lowerBinding === 'down' && key.downArrow) ||
54
- (lowerBinding === 'left' && key.leftArrow) ||
55
- (lowerBinding === 'right' && key.rightArrow) ||
56
- (lowerBinding === 'pageup' && key.pageUp) ||
57
- (lowerBinding === 'pagedown' && key.pageDown) ||
58
- (() => {
59
- const parts = lowerBinding.split('+');
60
- const hasCtrl = parts.includes('ctrl');
61
- const hasMeta = parts.includes('meta') || parts.includes('alt');
62
- const hasShift = parts.includes('shift');
63
- const mainKey = parts[parts.length - 1];
64
- if (hasCtrl && !key.ctrl)
65
- return false;
66
- if (hasMeta && !key.meta)
67
- return false;
68
- if (hasShift && !key.shift)
69
- return false;
70
- // Check arrow keys
71
- if (mainKey === 'up' && key.upArrow)
72
- return true;
73
- if (mainKey === 'down' && key.downArrow)
74
- return true;
75
- if (mainKey === 'left' && key.leftArrow)
76
- return true;
77
- if (mainKey === 'right' && key.rightArrow)
78
- return true;
79
- // Handle '=' and '+'
80
- if (mainKey === '=' && input === '=')
81
- return true;
82
- if (mainKey === '+' && input === '+')
83
- return true;
84
- if (mainKey === '+' && key.shift && input === '=')
85
- return true;
86
- return (input.toLowerCase() === mainKey && !key.ctrl && !key.meta);
87
- })();
88
- if (isMatch) {
89
- entry.handler();
90
- return;
91
- }
92
- }
93
- }
94
- }
95
- // If no bypass handler matched, skip all global shortcuts
96
- return;
97
- }
98
- // Debug logging for key presses (helps diagnose binding issues)
99
- if (input || key.ctrl || key.meta || key.shift) {
100
- logger.debug('KeyboardManager', 'Key pressed', {
101
- input,
102
- ctrl: key.ctrl,
103
- meta: key.meta,
104
- shift: key.shift,
105
- upArrow: key.upArrow,
106
- downArrow: key.downArrow,
107
- leftArrow: key.leftArrow,
108
- rightArrow: key.rightArrow,
109
- });
110
- }
111
- // Global quit handling
112
- if (key.ctrl && input === 'c') {
113
- // Exit cleanly without clearing screen (let Ink handle cleanup)
114
- process.exit(0);
115
- }
116
- // Note: Ctrl+L refresh removed to fix scroll-to-top issue
117
- // Direct ANSI escapes bypass Ink's rendering and cause scrolling problems
118
- // Dispatch to all registered handlers
119
- for (const entry of registry) {
120
- const { keys, handler } = entry;
121
- for (const binding of keys) {
122
- const lowerBinding = binding.toLowerCase();
123
- // Handle special keys
124
- const isMatch = (lowerBinding === 'escape' && key.escape) ||
125
- ((lowerBinding === 'return' || lowerBinding === 'enter') &&
126
- key.return) ||
127
- (lowerBinding === 'backspace' && key.backspace) ||
128
- (lowerBinding === 'tab' && key.tab) ||
129
- (lowerBinding === 'up' && key.upArrow) ||
130
- (lowerBinding === 'down' && key.downArrow) ||
131
- (lowerBinding === 'left' && key.leftArrow) ||
132
- (lowerBinding === 'right' && key.rightArrow) ||
133
- (lowerBinding === 'pageup' && key.pageUp) ||
134
- (lowerBinding === 'pagedown' && key.pageDown) ||
135
- // Handle combinations
136
- (() => {
137
- const parts = lowerBinding.split('+');
138
- const hasCtrl = parts.includes('ctrl');
139
- const hasMeta = parts.includes('meta') || parts.includes('alt');
140
- const hasShift = parts.includes('shift');
141
- const mainKey = parts[parts.length - 1];
142
- const uppercaseShiftInput = input.length === 1 &&
143
- input === input.toUpperCase() &&
144
- input.toLowerCase() === mainKey;
145
- if (hasCtrl && !key.ctrl)
146
- return false;
147
- if (hasMeta && !key.meta)
148
- return false;
149
- if (hasShift && !key.shift && !uppercaseShiftInput)
150
- return false;
151
- // Block lowercase-only bindings when shift is active or the input is
152
- // an uppercase letter (which implies Shift was held).
153
- // Example: the 'p' (Plugins) binding must not fire when the user
154
- // presses Shift+P, which should only trigger 'shift+p' (Playlists).
155
- // Note: `input !== input.toLowerCase()` is true only for uppercase
156
- // alphabetical characters, avoiding false positives on symbols/digits.
157
- if (!hasShift &&
158
- (key.shift ||
159
- (input.length === 1 && input !== input.toLowerCase())))
160
- return false;
161
- // Check the actual key
162
- if (mainKey === 'up' && key.upArrow)
163
- return true;
164
- if (mainKey === 'down' && key.downArrow)
165
- return true;
166
- if (mainKey === 'left' && key.leftArrow)
167
- return true;
168
- if (mainKey === 'right' && key.rightArrow)
169
- return true;
170
- // Handle '=' and '+' specially (+ is shift+=)
171
- if (mainKey === '=' && input === '=')
172
- return true;
173
- if (mainKey === '+' && input === '+')
174
- return true;
175
- if (mainKey === '+' && key.shift && input === '=')
176
- return true; // shift+= produces '+'
177
- return input.toLowerCase() === mainKey && !key.ctrl && !key.meta;
178
- })();
179
- if (isMatch) {
180
- handler();
181
- // We don't break here because multiple handlers might want to react
182
- // but usually only one does.
183
- }
184
- }
185
- }
186
- });
187
- return null;
188
- }
@@ -1,45 +0,0 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState, } from 'react';
3
- const KeyboardBlockContext = createContext(null);
4
- export function KeyboardBlockProvider({ children }) {
5
- const [blockCount, setBlockCount] = useState(0);
6
- const increment = useCallback(() => {
7
- setBlockCount(prev => prev + 1);
8
- }, []);
9
- const decrement = useCallback(() => {
10
- setBlockCount(prev => Math.max(0, prev - 1));
11
- }, []);
12
- const value = useMemo(() => ({ blockCount, increment, decrement }), [blockCount, increment, decrement]);
13
- return (_jsx(KeyboardBlockContext.Provider, { value: value, children: children }));
14
- }
15
- export function useKeyboardBlockContext() {
16
- const context = useContext(KeyboardBlockContext);
17
- if (!context) {
18
- throw new Error('useKeyboardBlockContext must be used within KeyboardBlockProvider');
19
- }
20
- return context;
21
- }
22
- export function useKeyboardBlocker(shouldBlock) {
23
- const { increment, decrement } = useKeyboardBlockContext();
24
- const blockedRef = useRef(false);
25
- useEffect(() => {
26
- if (shouldBlock && !blockedRef.current) {
27
- increment();
28
- blockedRef.current = true;
29
- }
30
- else if (!shouldBlock && blockedRef.current) {
31
- decrement();
32
- blockedRef.current = false;
33
- }
34
- return () => {
35
- if (blockedRef.current) {
36
- decrement();
37
- blockedRef.current = false;
38
- }
39
- };
40
- }, [shouldBlock, increment, decrement]);
41
- }
42
- export function useIsKeyboardBlocked() {
43
- const { blockCount } = useKeyboardBlockContext();
44
- return blockCount > 0;
45
- }
@@ -1,5 +0,0 @@
1
- // Navigation hook
2
- import { useNavigation as useNavigationStore } from "../stores/navigation.store.js";
3
- export function useNavigation() {
4
- return useNavigationStore();
5
- }
@@ -1,43 +0,0 @@
1
- // Player hook - audio playback orchestration
2
- import { useCallback } from 'react';
3
- import { usePlayer as usePlayerStore } from "../stores/player.store.js";
4
- import { getConfigService } from "../services/config/config.service.js";
5
- export function usePlayer() {
6
- const { state, dispatch, ...playerStore } = usePlayerStore();
7
- const play = useCallback((track, options) => {
8
- if (options?.clearQueue) {
9
- // When clearing the queue, always dispatch fresh play commands rather
10
- // than relying on the stale queue state captured in this closure.
11
- // This fixes a bug where a track already in the queue wouldn't replay
12
- // after clearQueue because SET_QUEUE_POSITION would be dispatched
13
- // against the (now-empty) queue.
14
- dispatch({ category: 'CLEAR_QUEUE' });
15
- dispatch({ category: 'ADD_TO_QUEUE', track });
16
- dispatch({ category: 'PLAY', track });
17
- }
18
- else {
19
- // Add to queue if not already there
20
- const isInQueue = state.queue.some(t => t.videoId === track.videoId);
21
- if (!isInQueue) {
22
- dispatch({ category: 'ADD_TO_QUEUE', track });
23
- }
24
- // Find position and play
25
- const position = state.queue.findIndex(t => t.videoId === track.videoId);
26
- if (position >= 0) {
27
- dispatch({ category: 'SET_QUEUE_POSITION', position });
28
- }
29
- else {
30
- dispatch({ category: 'PLAY', track });
31
- }
32
- }
33
- // Add to history
34
- const config = getConfigService();
35
- config.addToHistory(track.videoId);
36
- }, [state.queue, dispatch]);
37
- return {
38
- ...playerStore,
39
- state,
40
- dispatch,
41
- play,
42
- };
43
- }
@@ -1,65 +0,0 @@
1
- // Playlist management hook
2
- import { getConfigService } from "../services/config/config.service.js";
3
- import { useState, useCallback, useEffect } from 'react';
4
- export function usePlaylist() {
5
- const [playlists, setPlaylists] = useState([]);
6
- const configService = getConfigService();
7
- useEffect(() => {
8
- setPlaylists(configService.get('playlists'));
9
- }, []);
10
- const createPlaylist = useCallback((name, tracks = []) => {
11
- const newPlaylist = {
12
- playlistId: Date.now().toString(),
13
- name,
14
- tracks: tracks.map(track => ({ ...track })),
15
- };
16
- const updatedPlaylists = [...playlists, newPlaylist];
17
- setPlaylists(updatedPlaylists);
18
- configService.set('playlists', updatedPlaylists);
19
- return newPlaylist;
20
- }, [playlists, configService]);
21
- const deletePlaylist = useCallback((playlistId) => {
22
- const updatedPlaylists = playlists.filter(p => p.playlistId !== playlistId);
23
- setPlaylists(updatedPlaylists);
24
- configService.set('playlists', updatedPlaylists);
25
- }, [playlists, configService]);
26
- const addTrackToPlaylist = useCallback((playlistId, track, force = false) => {
27
- const playlistIndex = playlists.findIndex(p => p.playlistId === playlistId);
28
- if (playlistIndex === -1)
29
- return 'added';
30
- const playlist = playlists[playlistIndex];
31
- const isDuplicate = playlist.tracks.some(t => t.videoId === track.videoId);
32
- if (isDuplicate && !force) {
33
- return 'duplicate';
34
- }
35
- const updatedPlaylists = [...playlists];
36
- updatedPlaylists[playlistIndex].tracks.push(track);
37
- setPlaylists(updatedPlaylists);
38
- configService.set('playlists', updatedPlaylists);
39
- return 'added';
40
- }, [playlists, configService]);
41
- const renamePlaylist = useCallback((playlistId, newName) => {
42
- const updatedPlaylists = playlists.map(playlist => playlist.playlistId === playlistId
43
- ? { ...playlist, name: newName }
44
- : playlist);
45
- setPlaylists(updatedPlaylists);
46
- configService.set('playlists', updatedPlaylists);
47
- }, [playlists, configService]);
48
- const removeTrackFromPlaylist = useCallback((playlistId, trackIndex) => {
49
- const playlistIndex = playlists.findIndex(p => p.playlistId === playlistId);
50
- if (playlistIndex === -1)
51
- return;
52
- const updatedPlaylists = [...playlists];
53
- updatedPlaylists[playlistIndex].tracks.splice(trackIndex, 1);
54
- setPlaylists(updatedPlaylists);
55
- configService.set('playlists', updatedPlaylists);
56
- }, [playlists, configService]);
57
- return {
58
- playlists,
59
- createPlaylist,
60
- deletePlaylist,
61
- renamePlaylist,
62
- addTrackToPlaylist,
63
- removeTrackFromPlaylist,
64
- };
65
- }
@@ -1,76 +0,0 @@
1
- // Search hook
2
- import { getSearchService } from "../services/youtube-music/search.service.js";
3
- import { useState, useCallback } from 'react';
4
- export function useSearch() {
5
- const [isLoading, setIsLoading] = useState(false);
6
- const [error, setError] = useState(null);
7
- const searchService = getSearchService();
8
- const searchSongs = useCallback(async (query) => {
9
- setIsLoading(true);
10
- setError(null);
11
- try {
12
- const results = await searchService.searchSongs(query);
13
- return results;
14
- }
15
- catch (err) {
16
- setError(err instanceof Error ? err.message : 'Search failed');
17
- return [];
18
- }
19
- finally {
20
- setIsLoading(false);
21
- }
22
- }, [searchService]);
23
- const searchAlbums = useCallback(async (query) => {
24
- setIsLoading(true);
25
- setError(null);
26
- try {
27
- const results = await searchService.searchAlbums(query);
28
- return results;
29
- }
30
- catch (err) {
31
- setError(err instanceof Error ? err.message : 'Search failed');
32
- return [];
33
- }
34
- finally {
35
- setIsLoading(false);
36
- }
37
- }, [searchService]);
38
- const searchArtists = useCallback(async (query) => {
39
- setIsLoading(true);
40
- setError(null);
41
- try {
42
- const results = await searchService.searchArtists(query);
43
- return results;
44
- }
45
- catch (err) {
46
- setError(err instanceof Error ? err.message : 'Search failed');
47
- return [];
48
- }
49
- finally {
50
- setIsLoading(false);
51
- }
52
- }, [searchService]);
53
- const searchPlaylists = useCallback(async (query) => {
54
- setIsLoading(true);
55
- setError(null);
56
- try {
57
- const results = await searchService.searchPlaylists(query);
58
- return results;
59
- }
60
- catch (err) {
61
- setError(err instanceof Error ? err.message : 'Search failed');
62
- return [];
63
- }
64
- finally {
65
- setIsLoading(false);
66
- }
67
- }, [searchService]);
68
- return {
69
- isLoading,
70
- error,
71
- searchSongs,
72
- searchAlbums,
73
- searchArtists,
74
- searchPlaylists,
75
- };
76
- }
@@ -1,48 +0,0 @@
1
- // Hook for managing the sleep timer
2
- import { useState, useEffect, useCallback } from 'react';
3
- import { getSleepTimerService, SLEEP_TIMER_PRESETS, } from "../services/sleep-timer/sleep-timer.service.js";
4
- import { usePlayer } from "./usePlayer.js";
5
- export function useSleepTimer() {
6
- const { pause } = usePlayer();
7
- const timerService = getSleepTimerService();
8
- const [remainingSeconds, setRemainingSeconds] = useState(null);
9
- const [activeMinutes, setActiveMinutes] = useState(null);
10
- // Poll remaining time every second when active
11
- useEffect(() => {
12
- if (!timerService.isActive())
13
- return;
14
- const interval = setInterval(() => {
15
- const remaining = timerService.getRemainingSeconds();
16
- setRemainingSeconds(remaining);
17
- if (remaining === 0) {
18
- setActiveMinutes(null);
19
- clearInterval(interval);
20
- }
21
- }, 1000);
22
- return () => {
23
- clearInterval(interval);
24
- };
25
- }, [activeMinutes, timerService]);
26
- const startTimer = useCallback((minutes) => {
27
- setActiveMinutes(minutes);
28
- setRemainingSeconds(minutes * 60);
29
- timerService.start(minutes, () => {
30
- pause();
31
- setActiveMinutes(null);
32
- setRemainingSeconds(null);
33
- });
34
- }, [timerService, pause]);
35
- const cancelTimer = useCallback(() => {
36
- timerService.cancel();
37
- setActiveMinutes(null);
38
- setRemainingSeconds(null);
39
- }, [timerService]);
40
- return {
41
- isActive: timerService.isActive(),
42
- activeMinutes,
43
- remainingSeconds,
44
- startTimer,
45
- cancelTimer,
46
- presets: SLEEP_TIMER_PRESETS,
47
- };
48
- }
@@ -1,24 +0,0 @@
1
- import { useState, useEffect } from 'react';
2
- import { useStdout } from 'ink';
3
- export function useTerminalSize() {
4
- const { stdout } = useStdout();
5
- const [size, setSize] = useState({
6
- columns: stdout?.columns || 80,
7
- rows: stdout?.rows || 24,
8
- });
9
- useEffect(() => {
10
- if (!stdout)
11
- return;
12
- const onResize = () => {
13
- setSize({
14
- columns: stdout.columns,
15
- rows: stdout.rows,
16
- });
17
- };
18
- stdout.on('resize', onResize);
19
- return () => {
20
- stdout.off('resize', onResize);
21
- };
22
- }, [stdout]);
23
- return size;
24
- }
@@ -1,5 +0,0 @@
1
- // Theme management hook
2
- import { useTheme as useThemeContext } from "../contexts/theme.context.js";
3
- export function useTheme() {
4
- return useThemeContext();
5
- }
@@ -1,112 +0,0 @@
1
- import { getMusicService } from "../services/youtube-music/api.js";
2
- import { useState, useCallback } from 'react';
3
- export function useYouTubeMusic() {
4
- const [isLoading, setIsLoading] = useState(false);
5
- const [error, setError] = useState(null);
6
- const musicService = getMusicService();
7
- const search = useCallback(async (query, options = {}) => {
8
- setIsLoading(true);
9
- setError(null);
10
- try {
11
- const response = await musicService.search(query, options);
12
- return response;
13
- }
14
- catch (err) {
15
- setError(err instanceof Error ? err.message : 'Search failed');
16
- return null;
17
- }
18
- finally {
19
- setIsLoading(false);
20
- }
21
- }, [musicService]);
22
- const getTrack = useCallback(async (videoId) => {
23
- setIsLoading(true);
24
- setError(null);
25
- try {
26
- const track = await musicService.getTrack(videoId);
27
- return track;
28
- }
29
- catch (err) {
30
- setError(err instanceof Error ? err.message : 'Failed to get track');
31
- return null;
32
- }
33
- finally {
34
- setIsLoading(false);
35
- }
36
- }, [musicService]);
37
- const getAlbum = useCallback(async (albumId) => {
38
- setIsLoading(true);
39
- setError(null);
40
- try {
41
- const album = await musicService.getAlbum(albumId);
42
- return album;
43
- }
44
- catch (err) {
45
- setError(err instanceof Error ? err.message : 'Failed to get album');
46
- return null;
47
- }
48
- finally {
49
- setIsLoading(false);
50
- }
51
- }, [musicService]);
52
- const getArtist = useCallback(async (artistId) => {
53
- setIsLoading(true);
54
- setError(null);
55
- try {
56
- const artist = await musicService.getArtist(artistId);
57
- return artist;
58
- }
59
- catch (err) {
60
- setError(err instanceof Error ? err.message : 'Failed to get artist');
61
- return null;
62
- }
63
- finally {
64
- setIsLoading(false);
65
- }
66
- }, [musicService]);
67
- const getPlaylist = useCallback(async (playlistId) => {
68
- setIsLoading(true);
69
- setError(null);
70
- try {
71
- const playlist = await musicService.getPlaylist(playlistId);
72
- return playlist;
73
- }
74
- catch (err) {
75
- setError(err instanceof Error ? err.message : 'Failed to get playlist');
76
- return null;
77
- }
78
- finally {
79
- setIsLoading(false);
80
- }
81
- }, [musicService]);
82
- const getSuggestions = useCallback(async (trackId) => {
83
- setIsLoading(true);
84
- setError(null);
85
- try {
86
- const suggestions = await musicService.getSuggestions(trackId);
87
- return suggestions;
88
- }
89
- catch (err) {
90
- // Suppress YouTubeJS parsing errors (library limitation with YouTube's changing API)
91
- // These are not user-actionable and create noise in the UI
92
- const errorMessage = err instanceof Error ? err.message : 'Failed to get suggestions';
93
- if (!errorMessage.includes('ParsingError')) {
94
- setError(errorMessage);
95
- }
96
- return [];
97
- }
98
- finally {
99
- setIsLoading(false);
100
- }
101
- }, [musicService]);
102
- return {
103
- isLoading,
104
- error,
105
- search,
106
- getTrack,
107
- getAlbum,
108
- getArtist,
109
- getPlaylist,
110
- getSuggestions,
111
- };
112
- }