does not work

This commit is contained in:
Andrey Kondratev
2025-08-27 18:37:44 +05:00
parent 3d6836dc30
commit 98787a382e
17 changed files with 9526 additions and 245 deletions

View File

@@ -60,7 +60,7 @@ class Database {
addSearchHistory(userId, query) {
return new Promise((resolve, reject) => {
this.db.run(
`INSERT INTO search_history (user_id, query) VALUES (?, ?)`,
'INSERT INTO search_history (user_id, query) VALUES (?, ?)',
[userId, query],
function(err) {
if (err) reject(err);
@@ -73,7 +73,7 @@ class Database {
addDownload(userId, youtubeId, title, filePath) {
return new Promise((resolve, reject) => {
this.db.run(
`INSERT INTO downloads (user_id, youtube_id, title, file_path) VALUES (?, ?, ?, ?)`,
'INSERT INTO downloads (user_id, youtube_id, title, file_path) VALUES (?, ?, ?, ?)',
[userId, youtubeId, title, filePath],
function(err) {
if (err) reject(err);
@@ -86,7 +86,7 @@ class Database {
getUserByTelegramId(telegramId) {
return new Promise((resolve, reject) => {
this.db.get(
`SELECT * FROM users WHERE telegram_id = ?`,
'SELECT * FROM users WHERE telegram_id = ?',
[telegramId],
(err, row) => {
if (err) reject(err);

View File

@@ -3,14 +3,14 @@ const path = require('path');
const fs = require('fs');
const ffmpeg = require('fluent-ffmpeg');
const Database = require('./database');
const YouTubeService = require('./youtube');
const SoundCloudService = require('./soundcloud');
const app = express();
const port = process.env.PORT || 3000;
// Initialize services
const db = new Database();
const youtube = new YouTubeService();
const soundcloud = new SoundCloudService();
// Middleware
app.use(express.json());
@@ -48,7 +48,7 @@ app.post('/api/search', async (req, res) => {
}
}
const videos = await youtube.searchVideos(query.trim());
const videos = await soundcloud.searchTracks(query.trim());
res.json({ videos });
} catch (error) {
@@ -61,6 +61,7 @@ app.post('/api/search', async (req, res) => {
app.post('/api/convert', async (req, res) => {
try {
const { videoId, title, userId } = req.body;
console.log('Convert request received:', { videoId, title, userId });
if (!videoId) {
return res.status(400).json({ error: 'Video ID is required' });
@@ -73,49 +74,80 @@ app.post('/api/convert', async (req, res) => {
// Check if file already exists
if (fs.existsSync(outputPath)) {
console.log('File already exists, serving cached version');
const audioUrl = `${req.protocol}://${req.get('host')}/downloads/${filename}`;
return res.json({ audioUrl, title });
}
// Get audio stream from YouTube
const audioStream = await youtube.getAudioStream(videoId);
console.log(`Starting MP3 conversion for: ${title}`);
// Convert to MP3 using ffmpeg
await new Promise((resolve, reject) => {
ffmpeg(audioStream)
.audioCodec('libmp3lame')
.audioBitrate(128)
.format('mp3')
.output(outputPath)
.on('error', (err) => {
console.error('FFmpeg error:', err);
reject(err);
})
.on('end', () => {
console.log('Conversion finished:', filename);
resolve();
})
.run();
});
try {
// Get audio stream from YouTube
console.log(`Attempting to get audio stream for: ${videoId}`);
const audioStream = await soundcloud.getAudioStream(videoId);
console.log('Audio stream obtained, starting FFmpeg conversion...');
// Convert to MP3 using ffmpeg
await new Promise((resolve, reject) => {
const conversion = ffmpeg(audioStream)
.audioCodec('libmp3lame')
.audioBitrate('192k')
.audioChannels(2)
.audioFrequency(44100)
.format('mp3')
.output(outputPath)
.on('start', (command) => {
console.log('FFmpeg started:', command);
})
.on('progress', (progress) => {
if (progress.percent) {
console.log(`Conversion progress: ${Math.round(progress.percent)}%`);
}
})
.on('end', () => {
console.log('MP3 conversion completed successfully');
resolve();
})
.on('error', (err) => {
console.error('FFmpeg error:', err.message);
reject(err);
});
conversion.run();
});
// Save download record
if (userId && userId !== 'demo') {
try {
const user = await db.getUserByTelegramId(userId);
if (user) {
await db.addDownload(user.id, videoId, title, outputPath);
// Save download record
if (userId && userId !== 'demo') {
try {
const user = await db.getUserByTelegramId(userId);
if (user) {
await db.addDownload(user.id, videoId, title, outputPath);
}
} catch (dbError) {
console.error('Database error:', dbError);
}
} catch (dbError) {
console.error('Database error:', dbError);
}
const audioUrl = `${req.protocol}://${req.get('host')}/downloads/${filename}`;
console.log('Conversion successful, file available at:', audioUrl);
res.json({ audioUrl, title });
} catch (conversionError) {
console.error('Conversion failed for video:', videoId);
console.error('Error details:', conversionError.message);
console.error('Full error:', conversionError);
// Return error - no fallbacks for Telegram bot
return res.status(503).json({
error: 'MP3 conversion failed. This video may be restricted or unavailable for download.',
details: conversionError.message,
videoId: videoId
});
}
const audioUrl = `${req.protocol}://${req.get('host')}/downloads/${filename}`;
res.json({ audioUrl, title });
} catch (error) {
console.error('Conversion error:', error);
res.status(500).json({ error: 'Failed to convert video' });
console.error('Server error:', error);
res.status(500).json({ error: 'Failed to process request' });
}
});
@@ -159,8 +191,9 @@ setInterval(() => {
}, 60 * 60 * 1000); // Run every hour
app.listen(port, () => {
console.log(`Quixotic server running on port ${port}`);
console.log(`Downloads directory: ${downloadsDir}`);
console.log(`Quixotic server running on port ${port}`);
console.log(`Downloads directory: ${downloadsDir}`);
console.log(`Open in browser: http://localhost:${port}`);
});
module.exports = app;

101
src/soundcloud.js Normal file
View File

@@ -0,0 +1,101 @@
const scdl = require('soundcloud-downloader').default;
class SoundCloudService {
constructor() {
console.log('SoundCloud service initialized');
}
async searchTracks(query, maxResults = 10) {
try {
console.log(`Searching SoundCloud for: ${query}`);
// Search for tracks on SoundCloud
const tracks = await scdl.search({
query: query,
limit: maxResults,
resourceType: 'tracks'
});
if (!tracks || tracks.length === 0) {
console.log('No tracks found');
return [];
}
const trackResults = tracks.map(track => ({
id: track.id,
title: track.title,
channel: track.user?.username || 'Unknown Artist',
thumbnail: track.artwork_url || track.user?.avatar_url || 'https://via.placeholder.com/300x300?text=No+Image',
duration: Math.floor(track.duration / 1000) || 0, // Convert from ms to seconds
url: track.permalink_url,
streamable: track.streamable,
downloadable: track.downloadable
}));
console.log(`Found ${trackResults.length} tracks on SoundCloud`);
return trackResults;
} catch (error) {
console.error('SoundCloud search error:', error.message);
return [];
}
}
async getTrackInfo(trackId) {
try {
const track = await scdl.getInfo(trackId);
return {
title: track.title,
author: track.user?.username || 'Unknown',
length: Math.floor(track.duration / 1000),
available: track.streamable
};
} catch (error) {
console.error('Error getting track info:', error);
throw error;
}
}
async getAudioStream(trackId) {
try {
console.log(`Getting audio stream for track: ${trackId}`);
// Get track info first
const trackInfo = await scdl.getInfo(trackId);
if (!trackInfo.streamable) {
throw new Error('Track is not streamable');
}
console.log(`Track: ${trackInfo.title}`);
console.log(`Artist: ${trackInfo.user?.username || 'Unknown'}`);
console.log(`Duration: ${Math.floor(trackInfo.duration / 1000)}s`);
// Get audio stream
const stream = await scdl.download(trackId);
console.log('Audio stream obtained successfully from SoundCloud');
return stream;
} catch (error) {
console.error('SoundCloud download failed:', error.message);
// Try alternative approach
try {
console.log('Trying alternative SoundCloud method...');
const trackUrl = `https://soundcloud.com/track/${trackId}`;
const stream = await scdl.download(trackUrl);
console.log('Audio stream obtained with alternative method');
return stream;
} catch (fallbackError) {
console.error('Alternative method also failed:', fallbackError.message);
throw new Error(`SoundCloud download failed: ${error.message}`);
}
}
}
}
module.exports = SoundCloudService;

View File

@@ -1,173 +0,0 @@
const ytdl = require('ytdl-core');
const axios = require('axios');
class YouTubeService {
constructor() {
// Using YouTube's internal API endpoint for search (no API key needed)
this.searchEndpoint = 'https://www.youtube.com/youtubei/v1/search?key=AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8';
}
async searchVideos(query, maxResults = 10) {
try {
const response = await axios.post(this.searchEndpoint, {
context: {
client: {
clientName: 'WEB',
clientVersion: '2.20230728.00.00'
}
},
query: query
});
const contents = response.data?.contents?.twoColumnSearchResultsRenderer?.primaryContents?.sectionListRenderer?.contents;
if (!contents) return [];
const videoResults = [];
for (const section of contents) {
const itemSection = section?.itemSectionRenderer?.contents;
if (!itemSection) continue;
for (const item of itemSection) {
const videoRenderer = item?.videoRenderer;
if (!videoRenderer) continue;
const video = this.parseVideoRenderer(videoRenderer);
if (video && videoResults.length < maxResults) {
videoResults.push(video);
}
}
}
return videoResults;
} catch (error) {
console.error('YouTube search error:', error.message);
// Fallback to alternative method
return this.searchWithYtdl(query, maxResults);
}
}
parseVideoRenderer(videoRenderer) {
try {
const videoId = videoRenderer.videoId;
if (!videoId) return null;
const title = videoRenderer.title?.runs?.[0]?.text || 'Unknown Title';
const channel = videoRenderer.ownerText?.runs?.[0]?.text || 'Unknown Channel';
// Get thumbnail
const thumbnails = videoRenderer.thumbnail?.thumbnails || [];
const thumbnail = thumbnails[thumbnails.length - 1]?.url ||
`https://img.youtube.com/vi/${videoId}/maxresdefault.jpg`;
// Parse duration
let duration = 0;
const durationText = videoRenderer.lengthText?.simpleText;
if (durationText) {
duration = this.parseDuration(durationText);
}
return {
id: videoId,
title,
channel,
thumbnail: thumbnail.startsWith('//') ? 'https:' + thumbnail : thumbnail,
duration,
url: `https://www.youtube.com/watch?v=${videoId}`
};
} catch (error) {
console.error('Error parsing video:', error);
return null;
}
}
async searchWithYtdl(query, maxResults = 10) {
// Fallback method using ytdl-core with search
try {
const searchUrl = `https://www.youtube.com/results?search_query=${encodeURIComponent(query)}`;
const response = await axios.get(searchUrl);
const videoIds = this.extractVideoIds(response.data);
const results = [];
for (let i = 0; i < Math.min(videoIds.length, maxResults); i++) {
try {
const info = await ytdl.getBasicInfo(videoIds[i]);
const videoDetails = info.videoDetails;
results.push({
id: videoDetails.videoId,
title: videoDetails.title,
channel: videoDetails.ownerChannelName || 'Unknown Channel',
thumbnail: videoDetails.thumbnails?.[0]?.url ||
`https://img.youtube.com/vi/${videoDetails.videoId}/maxresdefault.jpg`,
duration: parseInt(videoDetails.lengthSeconds) || 0,
url: videoDetails.video_url
});
} catch (error) {
console.error(`Error getting info for video ${videoIds[i]}:`, error.message);
}
}
return results;
} catch (error) {
console.error('Fallback search error:', error.message);
return [];
}
}
extractVideoIds(html) {
const videoIdRegex = /"videoId":"([^"]+)"/g;
const videoIds = [];
let match;
while ((match = videoIdRegex.exec(html)) !== null) {
if (!videoIds.includes(match[1])) {
videoIds.push(match[1]);
}
}
return videoIds;
}
parseDuration(durationText) {
const parts = durationText.split(':').reverse();
let seconds = 0;
for (let i = 0; i < parts.length; i++) {
seconds += parseInt(parts[i]) * Math.pow(60, i);
}
return seconds;
}
async getVideoInfo(videoId) {
try {
const info = await ytdl.getBasicInfo(videoId);
return {
title: info.videoDetails.title,
author: info.videoDetails.author.name,
length: info.videoDetails.lengthSeconds,
formats: info.formats
};
} catch (error) {
console.error('Error getting video info:', error);
throw error;
}
}
async getAudioStream(videoId) {
try {
const stream = ytdl(videoId, {
quality: 'highestaudio',
filter: 'audioonly'
});
return stream;
} catch (error) {
console.error('Error getting audio stream:', error);
throw error;
}
}
}
module.exports = YouTubeService;