@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,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,2 +0,0 @@
1
- // Playlist import type definitions
2
- 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,2 +0,0 @@
1
- // Web UI and WebSocket type definitions
2
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1,3 +0,0 @@
1
- // Type definitions for youtubei.js API responses
2
- // These interfaces provide proper typing for the dynamic responses from the YouTube API
3
- 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
- }