From 913f833b8cf2c2c0e9c19295f4b0a5190a033fcf Mon Sep 17 00:00:00 2001 From: Andrey Kondratev Date: Thu, 28 Aug 2025 13:41:56 +0500 Subject: [PATCH] working! --- package.json | 3 +++ public/script.ts | 8 +++--- src/server.ts | 4 +-- src/soundcloud.ts | 68 ++++++++++++++++++++++++++++++++++++++--------- 4 files changed, 66 insertions(+), 17 deletions(-) diff --git a/package.json b/package.json index 432c840..ce8bf20 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,9 @@ "start": "node dist/server.js", "dev": "ts-node src/server.ts", "dev:watch": "nodemon --exec ts-node src/server.ts", + "dev:bg": "nohup ts-node src/server.ts > server.log 2>&1 & echo $! > server.pid && echo 'Server started in background, PID saved to server.pid'", + "stop": "if [ -f server.pid ]; then kill $(cat server.pid) && rm server.pid && echo 'Server stopped'; else echo 'No PID file found'; fi", + "logs": "tail -f server.log", "lint": "eslint src/ public/ --ext .ts,.js", "lint:fix": "eslint src/ public/ --ext .ts,.js --fix", "validate": "npm run lint && npm run build && echo '✅ All checks passed!'", diff --git a/public/script.ts b/public/script.ts index c7457a6..ceeaf6b 100644 --- a/public/script.ts +++ b/public/script.ts @@ -25,6 +25,7 @@ interface VideoResult { channel: string; thumbnail: string; duration: number; + url: string; } interface SearchResponse { @@ -124,7 +125,7 @@ class QuixoticApp { } this.results.innerHTML = videos.map(video => ` -
+
${this.escapeHtml(video.title)}
${this.escapeHtml(video.title)}
@@ -144,8 +145,8 @@ class QuixoticApp { this.noResults.classList.remove('hidden'); } - public async convertVideo(videoId: string, title: string): Promise { - console.log('Convert video called:', { videoId, title }); + public async convertVideo(videoId: string, title: string, url: string): Promise { + console.log('Convert video called:', { videoId, title, url }); const videoElement = (event as any)?.currentTarget as HTMLElement; if (videoElement) { videoElement.classList.add('converting'); @@ -161,6 +162,7 @@ class QuixoticApp { body: JSON.stringify({ videoId, title, + url, userId: this.tg?.initDataUnsafe?.user?.id || 'demo' }) }); diff --git a/src/server.ts b/src/server.ts index 39dd930..00a3090 100644 --- a/src/server.ts +++ b/src/server.ts @@ -60,7 +60,7 @@ app.post('/api/search', async (req: Request, res: Response) => { // Convert video to MP3 app.post('/api/convert', async (req: Request, res: Response) => { try { - const { videoId, title, userId }: { videoId?: string; title?: string; userId?: string } = req.body; + const { videoId, title, userId, url }: { videoId?: string; title?: string; userId?: string; url?: string } = req.body; console.log('Convert request received:', { videoId, title, userId }); if (!videoId) { @@ -84,7 +84,7 @@ app.post('/api/convert', async (req: Request, res: Response) => { try { // Get audio stream from YouTube console.log(`Attempting to get audio stream for: ${videoId}`); - const audioStream = await soundcloud.getAudioStream(videoId); + const audioStream = await soundcloud.getAudioStream(videoId, url); console.log('Audio stream obtained, starting FFmpeg conversion...'); // Convert to MP3 using ffmpeg diff --git a/src/soundcloud.ts b/src/soundcloud.ts index e7d7e7e..1433415 100644 --- a/src/soundcloud.ts +++ b/src/soundcloud.ts @@ -43,12 +43,38 @@ export class SoundCloudService { console.log(`Searching SoundCloud for: ${query}`); // Search for tracks on SoundCloud - const tracks = await scdl.search({ + const searchResult = await scdl.search({ query: query, limit: maxResults, resourceType: 'tracks' }) as any; + console.log('Search result type:', typeof searchResult); + console.log('Search result:', searchResult); + + // Handle different response formats + let tracks: any[] = []; + + if (Array.isArray(searchResult)) { + tracks = searchResult; + } else if (searchResult && searchResult.collection && Array.isArray(searchResult.collection)) { + tracks = searchResult.collection; + } else if (searchResult && searchResult.tracks && Array.isArray(searchResult.tracks)) { + tracks = searchResult.tracks; + } else if (searchResult && typeof searchResult === 'object') { + // Try to find any array property that might contain tracks + const keys = Object.keys(searchResult); + for (const key of keys) { + if (Array.isArray(searchResult[key]) && searchResult[key].length > 0) { + const firstItem = searchResult[key][0]; + if (firstItem && (firstItem.id || firstItem.title || firstItem.permalink_url)) { + tracks = searchResult[key]; + break; + } + } + } + } + if (!tracks || tracks.length === 0) { console.log('No tracks found'); return []; @@ -89,11 +115,19 @@ export class SoundCloudService { } } - async getAudioStream(trackId: string | number): Promise { + async getAudioStream(trackId: string | number, trackUrl?: string): Promise { try { console.log(`Getting audio stream for track: ${trackId}`); - // Get track info first + // If trackUrl is provided, use it directly + if (trackUrl) { + console.log(`Using provided track URL: ${trackUrl}`); + const stream = await scdl.download(trackUrl); + console.log('Audio stream obtained successfully from SoundCloud using URL'); + return stream; + } + + // Get track info first if no URL provided const trackInfo = await scdl.getInfo(String(trackId)) as SearchTrack; if (!trackInfo.streamable) { @@ -104,8 +138,8 @@ export class SoundCloudService { console.log(`Artist: ${trackInfo.user?.username || 'Unknown'}`); console.log(`Duration: ${Math.floor(trackInfo.duration / 1000)}s`); - // Get audio stream - const stream = await scdl.download(String(trackId)); + // Use the permalink_url from track info + const stream = await scdl.download(trackInfo.permalink_url); console.log('Audio stream obtained successfully from SoundCloud'); return stream; @@ -113,18 +147,28 @@ export class SoundCloudService { } catch (error: any) { console.error('SoundCloud download failed:', error.message); - // Try alternative approach + // Try alternative approaches try { - console.log('Trying alternative SoundCloud method...'); - const trackUrl = `https://soundcloud.com/track/${trackId}`; - const stream = await scdl.download(trackUrl); + console.log('Trying alternative SoundCloud methods...'); - console.log('Audio stream obtained with alternative method'); + // Try with track ID directly + const stream = await scdl.download(String(trackId)); + console.log('Audio stream obtained with track ID method'); return stream; } catch (fallbackError: any) { - console.error('Alternative method also failed:', fallbackError.message); - throw new Error(`SoundCloud download failed: ${error.message}`); + console.error('Track ID method failed, trying URL construction...'); + + // Final fallback - try constructing different URL formats + try { + const trackUrl = `https://soundcloud.com/${trackId}`; + const stream = await scdl.download(trackUrl); + console.log('Audio stream obtained with constructed URL method'); + return stream; + } catch (finalError: any) { + console.error('All methods failed:', finalError.message); + throw new Error(`SoundCloud download failed: ${error.message}`); + } } } }