@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.
- package/CHANGELOG.md +4 -0
- package/dist/cli.js.map +1004 -0
- package/dist/source/hooks/usePlayer.d.ts +1 -0
- package/dist/source/services/player-state/player-state.service.d.ts +1 -0
- package/dist/source/stores/player.store.d.ts +1 -0
- package/dist/source/types/actions.d.ts +4 -0
- package/dist/source/types/player.types.d.ts +3 -2
- package/dist/source/utils/constants.d.ts +1 -0
- package/dist/source/utils/icons.d.ts +1 -0
- package/dist/youtube-music-cli +0 -0
- package/package.json +1 -1
- package/dist/eslint.config.js +0 -55
- package/dist/package.json +0 -120
- package/dist/scripts/build-cli.js +0 -46
- package/dist/source/app.js +0 -17
- package/dist/source/cli.js +0 -504
- package/dist/source/components/common/ErrorBoundary.js +0 -22
- package/dist/source/components/common/Help.js +0 -18
- package/dist/source/components/common/ShortcutsBar.js +0 -80
- package/dist/source/components/config/ConfigLayout.js +0 -84
- package/dist/source/components/config/KeybindingsLayout.js +0 -107
- package/dist/source/components/export/ExportLayout.js +0 -111
- package/dist/source/components/import/ImportLayout.js +0 -119
- package/dist/source/components/import/ImportProgress.js +0 -73
- package/dist/source/components/layouts/ExploreLayout.js +0 -72
- package/dist/source/components/layouts/HistoryLayout.js +0 -37
- package/dist/source/components/layouts/LyricsLayout.js +0 -89
- package/dist/source/components/layouts/MainLayout.js +0 -190
- package/dist/source/components/layouts/MiniPlayerLayout.js +0 -20
- package/dist/source/components/layouts/PlayerLayout.js +0 -9
- package/dist/source/components/layouts/PluginsLayout.js +0 -77
- package/dist/source/components/layouts/SearchLayout.js +0 -193
- package/dist/source/components/layouts/TrendingLayout.js +0 -59
- package/dist/source/components/player/NowPlaying.js +0 -45
- package/dist/source/components/player/PlayerControls.js +0 -83
- package/dist/source/components/player/ProgressBar.js +0 -19
- package/dist/source/components/player/QueueList.js +0 -36
- package/dist/source/components/player/Suggestions.js +0 -50
- package/dist/source/components/playlist/PlaylistList.js +0 -138
- package/dist/source/components/plugins/PluginInstallDialog.js +0 -41
- package/dist/source/components/plugins/PluginsAvailable.js +0 -55
- package/dist/source/components/plugins/PluginsList.js +0 -18
- package/dist/source/components/search/SearchBar.js +0 -55
- package/dist/source/components/search/SearchHistory.js +0 -35
- package/dist/source/components/search/SearchResults.js +0 -280
- package/dist/source/components/settings/Settings.js +0 -211
- package/dist/source/components/theme/ThemeSwitcher.js +0 -11
- package/dist/source/config/themes.config.js +0 -123
- package/dist/source/contexts/theme.context.js +0 -29
- package/dist/source/hooks/useKeyboard.js +0 -188
- package/dist/source/hooks/useKeyboardBlocker.js +0 -45
- package/dist/source/hooks/useNavigation.js +0 -5
- package/dist/source/hooks/usePlayer.js +0 -43
- package/dist/source/hooks/usePlaylist.js +0 -65
- package/dist/source/hooks/useSearch.js +0 -76
- package/dist/source/hooks/useSleepTimer.js +0 -48
- package/dist/source/hooks/useTerminalSize.js +0 -24
- package/dist/source/hooks/useTheme.js +0 -5
- package/dist/source/hooks/useYouTubeMusic.js +0 -112
- package/dist/source/main.js +0 -127
- package/dist/source/services/cache/cache.service.js +0 -67
- package/dist/source/services/completions/completions.service.js +0 -313
- package/dist/source/services/config/config.service.js +0 -191
- package/dist/source/services/discord/discord-rpc.service.js +0 -95
- package/dist/source/services/download/download.service.js +0 -350
- package/dist/source/services/export/export.service.js +0 -131
- package/dist/source/services/history/history.service.js +0 -83
- package/dist/source/services/import/import.service.js +0 -272
- package/dist/source/services/import/spotify.service.js +0 -171
- package/dist/source/services/import/track-matcher.service.js +0 -271
- package/dist/source/services/import/youtube-import.service.js +0 -84
- package/dist/source/services/logger/logger.service.js +0 -52
- package/dist/source/services/lyrics/lyrics.service.js +0 -93
- package/dist/source/services/mpris/mpris.service.js +0 -78
- package/dist/source/services/notification/notification.service.js +0 -57
- package/dist/source/services/player/dependency-check.service.js +0 -140
- package/dist/source/services/player/player.service.js +0 -478
- package/dist/source/services/player-state/player-state.service.js +0 -122
- package/dist/source/services/plugin/plugin-audio-api.js +0 -36
- package/dist/source/services/plugin/plugin-context.js +0 -256
- package/dist/source/services/plugin/plugin-hooks.service.js +0 -135
- package/dist/source/services/plugin/plugin-installer.service.js +0 -248
- package/dist/source/services/plugin/plugin-loader.service.js +0 -161
- package/dist/source/services/plugin/plugin-permissions.service.js +0 -194
- package/dist/source/services/plugin/plugin-registry.service.js +0 -215
- package/dist/source/services/plugin/plugin-ui-api.js +0 -46
- package/dist/source/services/plugin/plugin-updater.service.js +0 -206
- package/dist/source/services/scrobbling/scrobbling.service.js +0 -115
- package/dist/source/services/sleep-timer/sleep-timer.service.js +0 -45
- package/dist/source/services/version-check/version-check.service.js +0 -121
- package/dist/source/services/web/static-file.service.js +0 -185
- package/dist/source/services/web/web-server-manager.js +0 -506
- package/dist/source/services/web/web-streaming.service.js +0 -290
- package/dist/source/services/web/websocket.server.js +0 -267
- package/dist/source/services/youtube-music/api.js +0 -649
- package/dist/source/services/youtube-music/search.service.js +0 -38
- package/dist/source/stores/history.store.js +0 -64
- package/dist/source/stores/navigation.store.js +0 -90
- package/dist/source/stores/player.store.js +0 -724
- package/dist/source/stores/plugins.store.js +0 -177
- package/dist/source/types/actions.js +0 -1
- package/dist/source/types/cli.types.js +0 -1
- package/dist/source/types/config.types.js +0 -1
- package/dist/source/types/history.types.js +0 -1
- package/dist/source/types/import.types.js +0 -2
- package/dist/source/types/keyboard.types.js +0 -1
- package/dist/source/types/navigation.types.js +0 -1
- package/dist/source/types/player.types.js +0 -1
- package/dist/source/types/playlist.types.js +0 -1
- package/dist/source/types/plugin.types.js +0 -1
- package/dist/source/types/theme.types.js +0 -1
- package/dist/source/types/web.types.js +0 -2
- package/dist/source/types/youtube-music.types.js +0 -1
- package/dist/source/types/youtubei.types.js +0 -3
- package/dist/source/utils/constants.js +0 -134
- package/dist/source/utils/format.js +0 -24
- package/dist/source/utils/icons.js +0 -26
- package/dist/source/utils/search-filters.js +0 -100
|
@@ -1,177 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
// Plugin store - manages plugin state in the TUI
|
|
3
|
-
import { createContext, useContext, useReducer, useMemo, useEffect, } from 'react';
|
|
4
|
-
import { getPluginRegistryService } from "../services/plugin/plugin-registry.service.js";
|
|
5
|
-
import { getPluginInstallerService } from "../services/plugin/plugin-installer.service.js";
|
|
6
|
-
import { getPluginUpdaterService } from "../services/plugin/plugin-updater.service.js";
|
|
7
|
-
const initialState = {
|
|
8
|
-
installedPlugins: [],
|
|
9
|
-
availablePlugins: [],
|
|
10
|
-
selectedIndex: 0,
|
|
11
|
-
isLoading: false,
|
|
12
|
-
error: null,
|
|
13
|
-
lastAction: null,
|
|
14
|
-
};
|
|
15
|
-
function pluginsReducer(state, action) {
|
|
16
|
-
switch (action.type) {
|
|
17
|
-
case 'SET_INSTALLED':
|
|
18
|
-
return { ...state, installedPlugins: action.plugins };
|
|
19
|
-
case 'SET_AVAILABLE':
|
|
20
|
-
return { ...state, availablePlugins: action.plugins };
|
|
21
|
-
case 'SET_SELECTED':
|
|
22
|
-
return { ...state, selectedIndex: action.index };
|
|
23
|
-
case 'SET_LOADING':
|
|
24
|
-
return { ...state, isLoading: action.loading };
|
|
25
|
-
case 'SET_ERROR':
|
|
26
|
-
return { ...state, error: action.error };
|
|
27
|
-
case 'SET_LAST_ACTION':
|
|
28
|
-
return { ...state, lastAction: action.action };
|
|
29
|
-
case 'REFRESH':
|
|
30
|
-
return { ...state };
|
|
31
|
-
default:
|
|
32
|
-
return state;
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
const PluginsContext = createContext(null);
|
|
36
|
-
export function PluginsProvider({ children }) {
|
|
37
|
-
const [state, dispatch] = useReducer(pluginsReducer, initialState);
|
|
38
|
-
const registryService = getPluginRegistryService();
|
|
39
|
-
const installerService = getPluginInstallerService();
|
|
40
|
-
const updaterService = getPluginUpdaterService();
|
|
41
|
-
const refreshPlugins = () => {
|
|
42
|
-
const plugins = registryService.getAllPlugins();
|
|
43
|
-
dispatch({ type: 'SET_INSTALLED', plugins });
|
|
44
|
-
};
|
|
45
|
-
const installPlugin = async (nameOrUrl) => {
|
|
46
|
-
dispatch({ type: 'SET_LOADING', loading: true });
|
|
47
|
-
dispatch({ type: 'SET_ERROR', error: null });
|
|
48
|
-
try {
|
|
49
|
-
let result;
|
|
50
|
-
if (nameOrUrl.startsWith('http')) {
|
|
51
|
-
result = await installerService.installFromGitHub(nameOrUrl);
|
|
52
|
-
}
|
|
53
|
-
else {
|
|
54
|
-
result = await installerService.installFromDefaultRepo(nameOrUrl);
|
|
55
|
-
}
|
|
56
|
-
if (result.success) {
|
|
57
|
-
dispatch({
|
|
58
|
-
type: 'SET_LAST_ACTION',
|
|
59
|
-
action: `Installed ${result.pluginId}`,
|
|
60
|
-
});
|
|
61
|
-
// Reload plugins
|
|
62
|
-
await registryService.loadAllPlugins();
|
|
63
|
-
refreshPlugins();
|
|
64
|
-
}
|
|
65
|
-
else {
|
|
66
|
-
dispatch({ type: 'SET_ERROR', error: result.error || 'Install failed' });
|
|
67
|
-
}
|
|
68
|
-
return result;
|
|
69
|
-
}
|
|
70
|
-
finally {
|
|
71
|
-
dispatch({ type: 'SET_LOADING', loading: false });
|
|
72
|
-
}
|
|
73
|
-
};
|
|
74
|
-
const uninstallPlugin = async (pluginId) => {
|
|
75
|
-
dispatch({ type: 'SET_LOADING', loading: true });
|
|
76
|
-
try {
|
|
77
|
-
// Unload from registry first
|
|
78
|
-
await registryService.unloadPlugin(pluginId);
|
|
79
|
-
// Then uninstall from disk
|
|
80
|
-
const result = await installerService.uninstall(pluginId);
|
|
81
|
-
if (result.success) {
|
|
82
|
-
dispatch({
|
|
83
|
-
type: 'SET_LAST_ACTION',
|
|
84
|
-
action: `Uninstalled ${pluginId}`,
|
|
85
|
-
});
|
|
86
|
-
refreshPlugins();
|
|
87
|
-
}
|
|
88
|
-
else {
|
|
89
|
-
dispatch({
|
|
90
|
-
type: 'SET_ERROR',
|
|
91
|
-
error: result.error || 'Uninstall failed',
|
|
92
|
-
});
|
|
93
|
-
}
|
|
94
|
-
return result;
|
|
95
|
-
}
|
|
96
|
-
finally {
|
|
97
|
-
dispatch({ type: 'SET_LOADING', loading: false });
|
|
98
|
-
}
|
|
99
|
-
};
|
|
100
|
-
const enablePlugin = async (pluginId) => {
|
|
101
|
-
try {
|
|
102
|
-
await registryService.enablePlugin(pluginId);
|
|
103
|
-
dispatch({ type: 'SET_LAST_ACTION', action: `Enabled ${pluginId}` });
|
|
104
|
-
refreshPlugins();
|
|
105
|
-
}
|
|
106
|
-
catch (error) {
|
|
107
|
-
dispatch({
|
|
108
|
-
type: 'SET_ERROR',
|
|
109
|
-
error: error instanceof Error ? error.message : 'Enable failed',
|
|
110
|
-
});
|
|
111
|
-
}
|
|
112
|
-
};
|
|
113
|
-
const disablePlugin = async (pluginId) => {
|
|
114
|
-
try {
|
|
115
|
-
await registryService.disablePlugin(pluginId);
|
|
116
|
-
dispatch({ type: 'SET_LAST_ACTION', action: `Disabled ${pluginId}` });
|
|
117
|
-
refreshPlugins();
|
|
118
|
-
}
|
|
119
|
-
catch (error) {
|
|
120
|
-
dispatch({
|
|
121
|
-
type: 'SET_ERROR',
|
|
122
|
-
error: error instanceof Error ? error.message : 'Disable failed',
|
|
123
|
-
});
|
|
124
|
-
}
|
|
125
|
-
};
|
|
126
|
-
const updatePlugin = async (pluginId) => {
|
|
127
|
-
dispatch({ type: 'SET_LOADING', loading: true });
|
|
128
|
-
try {
|
|
129
|
-
const result = await updaterService.updatePlugin(pluginId);
|
|
130
|
-
if (result.success) {
|
|
131
|
-
dispatch({
|
|
132
|
-
type: 'SET_LAST_ACTION',
|
|
133
|
-
action: `Updated ${pluginId} to ${result.newVersion}`,
|
|
134
|
-
});
|
|
135
|
-
// Reload the plugin
|
|
136
|
-
await registryService.loadAllPlugins();
|
|
137
|
-
refreshPlugins();
|
|
138
|
-
}
|
|
139
|
-
else {
|
|
140
|
-
dispatch({ type: 'SET_ERROR', error: result.error || 'Update failed' });
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
finally {
|
|
144
|
-
dispatch({ type: 'SET_LOADING', loading: false });
|
|
145
|
-
}
|
|
146
|
-
};
|
|
147
|
-
// Load plugins on mount
|
|
148
|
-
useEffect(() => {
|
|
149
|
-
const loadPlugins = async () => {
|
|
150
|
-
await registryService.loadAllPlugins();
|
|
151
|
-
const plugins = registryService.getAllPlugins();
|
|
152
|
-
dispatch({ type: 'SET_INSTALLED', plugins });
|
|
153
|
-
};
|
|
154
|
-
void loadPlugins();
|
|
155
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
156
|
-
}, []);
|
|
157
|
-
const contextValue = useMemo(() => ({
|
|
158
|
-
state,
|
|
159
|
-
dispatch,
|
|
160
|
-
refreshPlugins,
|
|
161
|
-
installPlugin,
|
|
162
|
-
uninstallPlugin,
|
|
163
|
-
enablePlugin,
|
|
164
|
-
disablePlugin,
|
|
165
|
-
updatePlugin,
|
|
166
|
-
}),
|
|
167
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
168
|
-
[state]);
|
|
169
|
-
return (_jsx(PluginsContext.Provider, { value: contextValue, children: children }));
|
|
170
|
-
}
|
|
171
|
-
export function usePlugins() {
|
|
172
|
-
const context = useContext(PluginsContext);
|
|
173
|
-
if (!context) {
|
|
174
|
-
throw new Error('usePlugins must be used within PluginsProvider');
|
|
175
|
-
}
|
|
176
|
-
return context;
|
|
177
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
import { readFileSync } from 'node:fs';
|
|
2
|
-
import { fileURLToPath } from 'node:url';
|
|
3
|
-
import { dirname, resolve } from 'node:path';
|
|
4
|
-
function loadAppVersion() {
|
|
5
|
-
if (typeof VERSION !== 'undefined') {
|
|
6
|
-
return VERSION;
|
|
7
|
-
}
|
|
8
|
-
let dir = dirname(fileURLToPath(import.meta.url));
|
|
9
|
-
for (let i = 0; i < 5; i++) {
|
|
10
|
-
try {
|
|
11
|
-
const content = readFileSync(resolve(dir, 'package.json'), 'utf8');
|
|
12
|
-
const pkg = JSON.parse(content);
|
|
13
|
-
if (typeof pkg.version === 'string' &&
|
|
14
|
-
pkg.name?.includes('youtube-music-cli')) {
|
|
15
|
-
return pkg.version;
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
catch {
|
|
19
|
-
/* ignore */
|
|
20
|
-
}
|
|
21
|
-
const parent = dirname(dir);
|
|
22
|
-
if (parent === dir)
|
|
23
|
-
break;
|
|
24
|
-
dir = parent;
|
|
25
|
-
}
|
|
26
|
-
return '0.0.0';
|
|
27
|
-
}
|
|
28
|
-
// Application constants
|
|
29
|
-
export const APP_NAME = '@involvex/youtube-music-cli';
|
|
30
|
-
export const APP_VERSION = loadAppVersion();
|
|
31
|
-
// Config directory
|
|
32
|
-
export const CONFIG_DIR = process.platform === 'win32'
|
|
33
|
-
? `${process.env['USERPROFILE']}\\.youtube-music-cli`
|
|
34
|
-
: `${process.env['HOME']}/.youtube-music-cli`;
|
|
35
|
-
export const CONFIG_FILE = `${CONFIG_DIR}/config.json`;
|
|
36
|
-
// View types
|
|
37
|
-
export const VIEW = {
|
|
38
|
-
PLAYER: 'player',
|
|
39
|
-
SEARCH: 'search',
|
|
40
|
-
SEARCH_HISTORY: 'search_history',
|
|
41
|
-
PLAYLISTS: 'playlists',
|
|
42
|
-
ARTIST: 'artist',
|
|
43
|
-
ALBUM: 'album',
|
|
44
|
-
HELP: 'help',
|
|
45
|
-
SUGGESTIONS: 'suggestions',
|
|
46
|
-
SETTINGS: 'settings',
|
|
47
|
-
CONFIG: 'config',
|
|
48
|
-
PLUGINS: 'plugins',
|
|
49
|
-
LYRICS: 'lyrics',
|
|
50
|
-
KEYBINDINGS: 'keybindings',
|
|
51
|
-
TRENDING: 'trending',
|
|
52
|
-
EXPLORE: 'explore',
|
|
53
|
-
IMPORT: 'import',
|
|
54
|
-
EXPORT_PLAYLISTS: 'export_playlists',
|
|
55
|
-
HISTORY: 'history',
|
|
56
|
-
};
|
|
57
|
-
// Search types
|
|
58
|
-
export const SEARCH_TYPE = {
|
|
59
|
-
ALL: 'all',
|
|
60
|
-
SONGS: 'songs',
|
|
61
|
-
ALBUMS: 'albums',
|
|
62
|
-
ARTISTS: 'artists',
|
|
63
|
-
PLAYLISTS: 'playlists',
|
|
64
|
-
};
|
|
65
|
-
// Keybindings
|
|
66
|
-
export const KEYBINDINGS = {
|
|
67
|
-
// Global
|
|
68
|
-
QUIT: ['q'],
|
|
69
|
-
HELP: ['?'],
|
|
70
|
-
SEARCH: ['/'],
|
|
71
|
-
PLAYLISTS: ['shift+p'],
|
|
72
|
-
SUGGESTIONS: ['g'],
|
|
73
|
-
HISTORY: ['shift+h'],
|
|
74
|
-
SETTINGS: [','],
|
|
75
|
-
PLUGINS: ['p'],
|
|
76
|
-
DETACH: ['shift+q'],
|
|
77
|
-
RESUME_BACKGROUND: ['shift+r'],
|
|
78
|
-
// Player
|
|
79
|
-
PLAY_PAUSE: [' '],
|
|
80
|
-
NEXT: ['n', 'right'],
|
|
81
|
-
PREVIOUS: ['b', 'left'],
|
|
82
|
-
VOLUME_UP: ['=', '+'], // '=' (no shift) or '+' (shift+=) both trigger volume up
|
|
83
|
-
VOLUME_DOWN: ['-'], // '-' triggers volume down
|
|
84
|
-
VOLUME_FINE_UP: ['ctrl+='], // Fine-grained +1 step (Ctrl+= to avoid conflict with '+')
|
|
85
|
-
VOLUME_FINE_DOWN: ['shift+-'], // Fine-grained -1 step
|
|
86
|
-
SHUFFLE: ['shift+s'],
|
|
87
|
-
REPEAT: ['r'],
|
|
88
|
-
GAPLESS_TOGGLE: ['shift+g'],
|
|
89
|
-
CROSSFADE_CYCLE: ['shift+c'],
|
|
90
|
-
EQUALIZER_CYCLE: ['shift+e'],
|
|
91
|
-
SEEK_FORWARD: ['shift+right'],
|
|
92
|
-
SEEK_BACKWARD: ['shift+left'],
|
|
93
|
-
SPEED_UP: ['>'],
|
|
94
|
-
SPEED_DOWN: ['<'],
|
|
95
|
-
// Navigation
|
|
96
|
-
UP: ['up', 'k'],
|
|
97
|
-
DOWN: ['down', 'j'],
|
|
98
|
-
SELECT: ['enter', 'return'],
|
|
99
|
-
BACK: ['escape'],
|
|
100
|
-
// Search
|
|
101
|
-
CLEAR_SEARCH: ['escape'],
|
|
102
|
-
NEXT_RESULT: ['tab'],
|
|
103
|
-
PREV_RESULT: ['shift+tab'],
|
|
104
|
-
INCREASE_RESULTS: [']'],
|
|
105
|
-
DECREASE_RESULTS: ['['],
|
|
106
|
-
SEARCH_FILTER_ARTIST: ['ctrl+a'],
|
|
107
|
-
SEARCH_FILTER_ALBUM: ['ctrl+l'],
|
|
108
|
-
SEARCH_FILTER_YEAR: ['ctrl+y'],
|
|
109
|
-
SEARCH_FILTER_DURATION: ['ctrl+d'],
|
|
110
|
-
// Playlist
|
|
111
|
-
ADD_TO_PLAYLIST: ['a'],
|
|
112
|
-
REMOVE_FROM_PLAYLIST: ['d'],
|
|
113
|
-
CREATE_PLAYLIST: ['c'],
|
|
114
|
-
CREATE_MIX: ['m'],
|
|
115
|
-
DELETE_PLAYLIST: ['D'],
|
|
116
|
-
DOWNLOAD: ['shift+d'],
|
|
117
|
-
};
|
|
118
|
-
// Default volume
|
|
119
|
-
export const DEFAULT_VOLUME = 70;
|
|
120
|
-
// Queue limits
|
|
121
|
-
export const MAX_QUEUE_SIZE = 1000;
|
|
122
|
-
// Themes
|
|
123
|
-
export const THEMES = {
|
|
124
|
-
DARK: 'dark',
|
|
125
|
-
LIGHT: 'light',
|
|
126
|
-
MIDNIGHT: 'midnight',
|
|
127
|
-
MATRIX: 'matrix',
|
|
128
|
-
CUSTOM: 'custom',
|
|
129
|
-
};
|
|
130
|
-
// Update intervals
|
|
131
|
-
export const PROGRESS_UPDATE_INTERVAL = 100; // ms
|
|
132
|
-
export const STATUS_UPDATE_INTERVAL = 1000; // ms
|
|
133
|
-
// Default theme
|
|
134
|
-
export const DEFAULT_THEME = 'dark';
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
// Format utilities
|
|
2
|
-
export function formatTime(seconds) {
|
|
3
|
-
if (!seconds || seconds < 0) {
|
|
4
|
-
return '0:00';
|
|
5
|
-
}
|
|
6
|
-
const mins = Math.floor(seconds / 60);
|
|
7
|
-
const secs = Math.floor(seconds % 60);
|
|
8
|
-
return `${mins}:${secs.toString().padStart(2, '0')}`;
|
|
9
|
-
}
|
|
10
|
-
export function formatNumber(num) {
|
|
11
|
-
if (num >= 1_000_000) {
|
|
12
|
-
return `${(num / 1_000_000).toFixed(1)}M`;
|
|
13
|
-
}
|
|
14
|
-
if (num >= 1_000) {
|
|
15
|
-
return `${(num / 1_000).toFixed(1)}K`;
|
|
16
|
-
}
|
|
17
|
-
return num.toString();
|
|
18
|
-
}
|
|
19
|
-
export function truncate(text, maxLength) {
|
|
20
|
-
if (text.length <= maxLength) {
|
|
21
|
-
return text;
|
|
22
|
-
}
|
|
23
|
-
return text.slice(0, maxLength - 3) + '...';
|
|
24
|
-
}
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
// Universal icon constants using widely-supported Unicode BMP characters.
|
|
2
|
-
// No emoji, no Nerd Font codepoints — renders correctly on any terminal font.
|
|
3
|
-
export const ICONS = {
|
|
4
|
-
// Playback controls
|
|
5
|
-
PLAY: '▶', // U+25B6
|
|
6
|
-
PAUSE: '‖', // U+2016
|
|
7
|
-
PLAY_PAUSE_ON: '▶', // when playing
|
|
8
|
-
PLAY_PAUSE_OFF: '‖', // when paused
|
|
9
|
-
NEXT: '▶|', // next track
|
|
10
|
-
PREV: '|◀', // previous track
|
|
11
|
-
// Playback modes
|
|
12
|
-
SHUFFLE: '⇄', // U+21C4
|
|
13
|
-
REPEAT_ALL: '↻', // U+21BB
|
|
14
|
-
REPEAT_ONE: '↺', // U+21BA
|
|
15
|
-
// Navigation / views
|
|
16
|
-
PLAYLIST: '☰', // U+2630
|
|
17
|
-
SEARCH: '/', // ASCII
|
|
18
|
-
HELP: '?', // ASCII
|
|
19
|
-
// Actions
|
|
20
|
-
DOWNLOAD: '↓', // U+2193
|
|
21
|
-
QUIT: '×', // U+00D7
|
|
22
|
-
RESUME: '⟳', // U+27F3
|
|
23
|
-
BG_PLAY: '○', // U+25CB
|
|
24
|
-
// Status
|
|
25
|
-
VOLUME: '♪', // U+266A
|
|
26
|
-
};
|
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
const DURATION_BUCKETS = {
|
|
2
|
-
short: { min: 0, max: 180 },
|
|
3
|
-
medium: { min: 181, max: 300 },
|
|
4
|
-
long: { min: 301, max: Number.POSITIVE_INFINITY },
|
|
5
|
-
};
|
|
6
|
-
function includesIgnoreCase(value, filter) {
|
|
7
|
-
return Boolean(value && value.toLowerCase().includes(filter));
|
|
8
|
-
}
|
|
9
|
-
function isSongResult(result) {
|
|
10
|
-
return result.type === 'song';
|
|
11
|
-
}
|
|
12
|
-
function isAlbumResult(result) {
|
|
13
|
-
return result.type === 'album';
|
|
14
|
-
}
|
|
15
|
-
function isArtistResult(result) {
|
|
16
|
-
return result.type === 'artist';
|
|
17
|
-
}
|
|
18
|
-
function isPlaylistResult(result) {
|
|
19
|
-
return result.type === 'playlist';
|
|
20
|
-
}
|
|
21
|
-
function matchesArtistFilter(result, filter) {
|
|
22
|
-
if (!filter)
|
|
23
|
-
return true;
|
|
24
|
-
if (isSongResult(result)) {
|
|
25
|
-
const track = result.data;
|
|
26
|
-
if (track.artists.some(artist => includesIgnoreCase(artist.name, filter))) {
|
|
27
|
-
return true;
|
|
28
|
-
}
|
|
29
|
-
if (track.album?.artists?.some(artist => includesIgnoreCase(artist.name, filter))) {
|
|
30
|
-
return true;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
if (isAlbumResult(result)) {
|
|
34
|
-
return result.data.artists.some(artist => includesIgnoreCase(artist.name, filter));
|
|
35
|
-
}
|
|
36
|
-
if (isArtistResult(result)) {
|
|
37
|
-
return includesIgnoreCase(result.data.name, filter);
|
|
38
|
-
}
|
|
39
|
-
if (isPlaylistResult(result)) {
|
|
40
|
-
return includesIgnoreCase(result.data.name, filter);
|
|
41
|
-
}
|
|
42
|
-
return true;
|
|
43
|
-
}
|
|
44
|
-
function matchesAlbumFilter(result, filter) {
|
|
45
|
-
if (!filter)
|
|
46
|
-
return true;
|
|
47
|
-
if (isSongResult(result)) {
|
|
48
|
-
return includesIgnoreCase(result.data.album?.name, filter);
|
|
49
|
-
}
|
|
50
|
-
if (isAlbumResult(result)) {
|
|
51
|
-
return includesIgnoreCase(result.data.name, filter);
|
|
52
|
-
}
|
|
53
|
-
if (isPlaylistResult(result)) {
|
|
54
|
-
return includesIgnoreCase(result.data.name, filter);
|
|
55
|
-
}
|
|
56
|
-
return true;
|
|
57
|
-
}
|
|
58
|
-
function matchesYearFilter(result, filter) {
|
|
59
|
-
if (!filter)
|
|
60
|
-
return true;
|
|
61
|
-
const normalizedFilter = filter.toLowerCase();
|
|
62
|
-
const textSources = [];
|
|
63
|
-
if (isSongResult(result)) {
|
|
64
|
-
textSources.push(result.data.title, result.data.album?.name);
|
|
65
|
-
}
|
|
66
|
-
if (isAlbumResult(result)) {
|
|
67
|
-
textSources.push(result.data.name);
|
|
68
|
-
textSources.push(...result.data.artists.map(artist => artist.name));
|
|
69
|
-
}
|
|
70
|
-
if (isArtistResult(result)) {
|
|
71
|
-
textSources.push(result.data.name);
|
|
72
|
-
}
|
|
73
|
-
if (isPlaylistResult(result)) {
|
|
74
|
-
textSources.push(result.data.name);
|
|
75
|
-
}
|
|
76
|
-
return textSources.some(source => includesIgnoreCase(source, normalizedFilter));
|
|
77
|
-
}
|
|
78
|
-
function matchesDurationFilter(result, filter) {
|
|
79
|
-
if (!filter || filter === 'all') {
|
|
80
|
-
return true;
|
|
81
|
-
}
|
|
82
|
-
if (!isSongResult(result)) {
|
|
83
|
-
return true;
|
|
84
|
-
}
|
|
85
|
-
const duration = result.data.duration ?? 0;
|
|
86
|
-
const range = DURATION_BUCKETS[filter];
|
|
87
|
-
return duration >= range.min && duration <= range.max;
|
|
88
|
-
}
|
|
89
|
-
export function applySearchFilters(results, filters) {
|
|
90
|
-
const artistFilter = filters.artist?.trim().toLowerCase() ?? '';
|
|
91
|
-
const albumFilter = filters.album?.trim().toLowerCase() ?? '';
|
|
92
|
-
const yearFilter = filters.year?.trim() ?? '';
|
|
93
|
-
const durationFilter = filters.duration;
|
|
94
|
-
return results.filter(result => {
|
|
95
|
-
return (matchesArtistFilter(result, artistFilter) &&
|
|
96
|
-
matchesAlbumFilter(result, albumFilter) &&
|
|
97
|
-
matchesYearFilter(result, yearFilter) &&
|
|
98
|
-
matchesDurationFilter(result, durationFilter));
|
|
99
|
-
});
|
|
100
|
-
}
|