@supasayan/ytm 1.0.0

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 (60) hide show
  1. package/bin/cli.js +44 -0
  2. package/client/index.html +13 -0
  3. package/client/package-lock.json +1003 -0
  4. package/client/package.json +22 -0
  5. package/client/public/favicon.svg +1 -0
  6. package/client/public/icons.svg +24 -0
  7. package/client/src/App.jsx +210 -0
  8. package/client/src/assets/hero.png +0 -0
  9. package/client/src/assets/typescript.svg +1 -0
  10. package/client/src/assets/vite.svg +1 -0
  11. package/client/src/components/Download/DownloadItem.css +246 -0
  12. package/client/src/components/Download/DownloadItem.jsx +108 -0
  13. package/client/src/components/Download/FlyingThumbnail.css +33 -0
  14. package/client/src/components/Download/FlyingThumbnail.jsx +48 -0
  15. package/client/src/components/Layout/Header.css +91 -0
  16. package/client/src/components/Layout/Header.jsx +49 -0
  17. package/client/src/components/Layout/SystemCheckScreen.css +250 -0
  18. package/client/src/components/Layout/SystemCheckScreen.jsx +110 -0
  19. package/client/src/components/Player/AudioPlayer.css +280 -0
  20. package/client/src/components/Player/AudioPlayer.jsx +154 -0
  21. package/client/src/components/Playlist/PlaylistInput.css +71 -0
  22. package/client/src/components/Playlist/PlaylistInput.jsx +44 -0
  23. package/client/src/components/Playlist/PlaylistView.css +150 -0
  24. package/client/src/components/Playlist/PlaylistView.jsx +104 -0
  25. package/client/src/components/Search/SearchBar.css +163 -0
  26. package/client/src/components/Search/SearchBar.jsx +127 -0
  27. package/client/src/components/Search/SearchResults.css +42 -0
  28. package/client/src/components/Search/SearchResults.jsx +64 -0
  29. package/client/src/components/Search/SongCard.css +296 -0
  30. package/client/src/components/Search/SongCard.jsx +204 -0
  31. package/client/src/components/Settings/SettingsPanel.css +345 -0
  32. package/client/src/components/Settings/SettingsPanel.jsx +174 -0
  33. package/client/src/hooks/useDownload.js +107 -0
  34. package/client/src/hooks/usePlayer.js +188 -0
  35. package/client/src/hooks/usePlaylist.js +52 -0
  36. package/client/src/hooks/useSearch.js +34 -0
  37. package/client/src/index.css +245 -0
  38. package/client/src/main.jsx +10 -0
  39. package/client/src/pages/DownloadsPage.css +37 -0
  40. package/client/src/pages/DownloadsPage.jsx +80 -0
  41. package/client/src/pages/PlaylistPage.jsx +46 -0
  42. package/client/src/pages/SearchPage.jsx +63 -0
  43. package/client/tsconfig.json +24 -0
  44. package/client/vite.config.js +12 -0
  45. package/main.js +79 -0
  46. package/package.json +48 -0
  47. package/server/index.js +54 -0
  48. package/server/package-lock.json +1428 -0
  49. package/server/package.json +14 -0
  50. package/server/routes/browse.js +23 -0
  51. package/server/routes/download.js +339 -0
  52. package/server/routes/playLocal.js +50 -0
  53. package/server/routes/playlist.js +28 -0
  54. package/server/routes/search.js +21 -0
  55. package/server/routes/stream.js +53 -0
  56. package/server/services/downloadService.js +143 -0
  57. package/server/services/ytmusicService.js +62 -0
  58. package/server/utils/dependencyChecker.js +174 -0
  59. package/server/utils/paths.js +11 -0
  60. package/server/utils/sanitize.js +9 -0
@@ -0,0 +1,63 @@
1
+ import { useSearch } from '../hooks/useSearch';
2
+ import { useEffect, useState, useCallback } from 'react';
3
+ import SearchBar from '../components/Search/SearchBar';
4
+ import SearchResults from '../components/Search/SearchResults';
5
+
6
+ export default function SearchPage({ outputDir, onDownload, onFlyAnimation, downloads, onPlay, currentSong, searchQueryPreset, onClearPreset }) {
7
+ const { results, isLoading, error, search } = useSearch();
8
+ const [isDefaultTrending, setIsDefaultTrending] = useState(true);
9
+
10
+ // Load world top trending English songs by default on mount
11
+ useEffect(() => {
12
+ search('Trending English Songs');
13
+ }, [search]);
14
+
15
+ // Sync state if searchQueryPreset is injected
16
+ useEffect(() => {
17
+ if (searchQueryPreset) {
18
+ setIsDefaultTrending(false);
19
+ }
20
+ }, [searchQueryPreset]);
21
+
22
+ const handleDownload = (song) => {
23
+ onDownload(song, outputDir);
24
+ };
25
+
26
+ const handleSearch = useCallback((query) => {
27
+ if (!query.trim()) {
28
+ setIsDefaultTrending(true);
29
+ search('Trending English Songs');
30
+ } else {
31
+ setIsDefaultTrending(false);
32
+ search(query);
33
+ }
34
+ }, [search]);
35
+
36
+ return (
37
+ <div className="search-page">
38
+ <SearchBar
39
+ onSearch={handleSearch}
40
+ searchQueryPreset={searchQueryPreset}
41
+ onClearPreset={onClearPreset}
42
+ isLoading={isLoading}
43
+ />
44
+
45
+ {error && (
46
+ <div className="error-message">
47
+ <p>⚠️ {error}</p>
48
+ </div>
49
+ )}
50
+
51
+ <SearchResults
52
+ results={results}
53
+ isLoading={isLoading}
54
+ onDownload={handleDownload}
55
+ onFlyAnimation={onFlyAnimation}
56
+ downloads={downloads}
57
+ onPlay={onPlay}
58
+ currentSong={currentSong}
59
+ isDefaultTrending={isDefaultTrending}
60
+ />
61
+ </div>
62
+ );
63
+ }
@@ -0,0 +1,24 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "es2023",
4
+ "module": "esnext",
5
+ "lib": ["ES2023", "DOM"],
6
+ "types": ["vite/client"],
7
+ "allowArbitraryExtensions": true,
8
+ "skipLibCheck": true,
9
+
10
+ /* Bundler mode */
11
+ "moduleResolution": "bundler",
12
+ "allowImportingTsExtensions": true,
13
+ "verbatimModuleSyntax": true,
14
+ "moduleDetection": "force",
15
+ "noEmit": true,
16
+
17
+ /* Linting */
18
+ "noUnusedLocals": true,
19
+ "noUnusedParameters": true,
20
+ "erasableSyntaxOnly": true,
21
+ "noFallthroughCasesInSwitch": true
22
+ },
23
+ "include": ["src"]
24
+ }
@@ -0,0 +1,12 @@
1
+ import { defineConfig } from 'vite'
2
+ import react from '@vitejs/plugin-react'
3
+
4
+ // https://vitejs.dev/config/
5
+ export default defineConfig({
6
+ plugins: [react()],
7
+ server: {
8
+ proxy: {
9
+ '/api': 'http://127.0.0.1:3001'
10
+ }
11
+ }
12
+ })
package/main.js ADDED
@@ -0,0 +1,79 @@
1
+ const { app, BrowserWindow } = require('electron');
2
+ const path = require('path');
3
+ const { fork } = require('child_process');
4
+
5
+ let serverProcess = null;
6
+ let mainWindow = null;
7
+
8
+ function startBackend() {
9
+ const serverPath = path.join(__dirname, 'server/index.js');
10
+
11
+ // Fork the Express backend process
12
+ serverProcess = fork(serverPath, [], {
13
+ env: {
14
+ ...process.env,
15
+ NODE_ENV: 'production',
16
+ PORT: '3001'
17
+ }
18
+ });
19
+
20
+ serverProcess.on('exit', (code, signal) => {
21
+ console.log(`Backend process exited with code ${code} and signal ${signal}`);
22
+ });
23
+
24
+ serverProcess.on('error', (err) => {
25
+ console.error('Backend process error:', err);
26
+ });
27
+ }
28
+
29
+ function createWindow() {
30
+ mainWindow = new BrowserWindow({
31
+ width: 1200,
32
+ height: 800,
33
+ title: 'YTM',
34
+ webPreferences: {
35
+ nodeIntegration: false,
36
+ contextIsolation: true
37
+ }
38
+ });
39
+
40
+ const isDev = !app.isPackaged;
41
+
42
+ const loadURL = () => {
43
+ const url = isDev ? 'http://127.0.0.1:5173' : 'http://127.0.0.1:3001';
44
+ mainWindow.loadURL(url).catch((err) => {
45
+ console.log(`Frontend port not ready yet, retrying in 200ms...`);
46
+ setTimeout(loadURL, 200);
47
+ });
48
+ };
49
+
50
+ loadURL();
51
+
52
+ mainWindow.on('closed', () => {
53
+ mainWindow = null;
54
+ });
55
+ }
56
+
57
+ app.whenReady().then(() => {
58
+ startBackend();
59
+ createWindow();
60
+
61
+ app.on('activate', () => {
62
+ if (BrowserWindow.getAllWindows().length === 0) {
63
+ createWindow();
64
+ }
65
+ });
66
+ });
67
+
68
+ app.on('window-all-closed', () => {
69
+ if (process.platform !== 'darwin') {
70
+ app.quit();
71
+ }
72
+ });
73
+
74
+ app.on('will-quit', () => {
75
+ if (serverProcess) {
76
+ console.log('[Electron] Killing backend child process...');
77
+ serverProcess.kill('SIGTERM');
78
+ }
79
+ });
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "@supasayan/ytm",
3
+ "version": "1.0.0",
4
+ "description": "Root package to run both client and server",
5
+ "main": "main.js",
6
+ "productName": "YTM",
7
+ "bin": {
8
+ "ytm": "./bin/cli.js"
9
+ },
10
+ "scripts": {
11
+ "start": "npx concurrently \"npm run start --prefix server\" \"npm run dev --prefix client\"",
12
+ "dev": "npx concurrently \"npm run dev --prefix server\" \"npm run dev --prefix client\"",
13
+ "install-all": "npm install --prefix server && npm install --prefix client",
14
+ "app:dev": "npx concurrently \"npm run dev --prefix server\" \"npm run dev --prefix client\" \"npx wait-on http://localhost:5173 && npx wait-on http://localhost:3001 && npx electron .\"",
15
+ "app:build": "npm run build --prefix client && npx electron-builder --mac"
16
+ },
17
+ "build": {
18
+ "appId": "com.sayan.ytm",
19
+ "productName": "YTM",
20
+ "directories": {
21
+ "output": "dist-app"
22
+ },
23
+ "mac": {
24
+ "target": [
25
+ "dmg"
26
+ ],
27
+ "category": "public.app-category.music"
28
+ },
29
+ "dmg": {
30
+ "title": "Install YTM"
31
+ },
32
+ "asar": true,
33
+ "asarUnpack": [
34
+ "**/node_modules/ffmpeg-static/**/*"
35
+ ],
36
+ "files": [
37
+ "main.js",
38
+ "server/**/*",
39
+ "client/dist/**/*",
40
+ "package.json"
41
+ ]
42
+ },
43
+ "devDependencies": {
44
+ "electron": "^43.1.0",
45
+ "electron-builder": "^26.15.3",
46
+ "wait-on": "^9.0.10"
47
+ }
48
+ }
@@ -0,0 +1,54 @@
1
+ import express from 'express';
2
+ import cors from 'cors';
3
+ import path from 'path';
4
+ import os from 'os';
5
+ import { fileURLToPath } from 'url';
6
+
7
+ import searchRoutes from './routes/search.js';
8
+ import playlistRoutes from './routes/playlist.js';
9
+ import downloadRoutes from './routes/download.js';
10
+ import browseRoutes from './routes/browse.js';
11
+ import streamRoutes from './routes/stream.js';
12
+ import playLocalRoutes from './routes/playLocal.js';
13
+ import { checkAndSetupDependencies, getDependencyStatus } from './utils/dependencyChecker.js';
14
+
15
+ const __filename = fileURLToPath(import.meta.url);
16
+ const __dirname = path.dirname(__filename);
17
+
18
+ const app = express();
19
+ const PORT = process.env.PORT || 3001;
20
+
21
+ // Middleware
22
+ app.use(cors());
23
+ app.use(express.json());
24
+
25
+ // Serve static assets from the client build folder in production
26
+ const clientDistPath = path.join(__dirname, '../client/dist');
27
+ app.use(express.static(clientDistPath));
28
+
29
+ // Routes
30
+ app.use('/api/search', searchRoutes);
31
+ app.use('/api/playlist', playlistRoutes);
32
+ app.use('/api/download', downloadRoutes);
33
+ app.use('/api/browse', browseRoutes);
34
+ app.use('/api/stream', streamRoutes);
35
+ app.use('/api/play-local', playLocalRoutes);
36
+
37
+ // System Check Endpoint
38
+ app.get('/api/system-check', (req, res) => {
39
+ res.json({
40
+ ...getDependencyStatus(),
41
+ defaultOutputDir: path.join(os.homedir(), 'Downloads')
42
+ });
43
+ });
44
+
45
+ // Fallback for Single Page App routing (redirect all non-api routes to index.html)
46
+ app.get('*', (req, res) => {
47
+ res.sendFile(path.join(clientDistPath, 'index.html'));
48
+ });
49
+
50
+ app.listen(PORT, async () => {
51
+ console.log(`YTMDownloader Backend running on http://127.0.0.1:${PORT}`);
52
+ // Run async setup check
53
+ await checkAndSetupDependencies();
54
+ });