new ui?!
This commit is contained in:
158
src/bot.ts
158
src/bot.ts
@@ -1,5 +1,6 @@
|
||||
import TelegramBot from 'node-telegram-bot-api';
|
||||
import { Database } from './database';
|
||||
import { logger } from './logger';
|
||||
|
||||
interface TelegramUser {
|
||||
id: number;
|
||||
@@ -49,7 +50,7 @@ export class QuixoticBot {
|
||||
const useWebhook = process.env.NODE_ENV === 'production' && process.env.WEBHOOK_URL;
|
||||
|
||||
if (useWebhook) {
|
||||
console.log('🌐 Using webhook mode for production');
|
||||
logger.telegram('Using webhook mode for production');
|
||||
this.bot = new TelegramBot(token, {
|
||||
webHook: {
|
||||
port: 8443,
|
||||
@@ -57,7 +58,7 @@ export class QuixoticBot {
|
||||
}
|
||||
});
|
||||
} else {
|
||||
console.log('🔄 Using polling mode for development');
|
||||
logger.telegram('Using polling mode for development');
|
||||
this.bot = new TelegramBot(token, { polling: true });
|
||||
}
|
||||
|
||||
@@ -67,7 +68,7 @@ export class QuixoticBot {
|
||||
}
|
||||
|
||||
private init(): void {
|
||||
console.log('🤖 Telegram bot initialized');
|
||||
logger.telegram('Bot initialized');
|
||||
this.setupCommands();
|
||||
this.setupHandlers();
|
||||
}
|
||||
@@ -82,7 +83,7 @@ export class QuixoticBot {
|
||||
}
|
||||
|
||||
private setupHandlers(): void {
|
||||
console.log('🔧 Setting up bot handlers...');
|
||||
logger.telegram('Setting up bot handlers...');
|
||||
|
||||
// Handle messages
|
||||
this.bot.on('message', (msg: any) => {
|
||||
@@ -108,21 +109,21 @@ export class QuixoticBot {
|
||||
const keyboard = {
|
||||
inline_keyboard: [[
|
||||
{
|
||||
text: '🎵 Открыть Quixotic',
|
||||
text: 'Открыть Quixotic',
|
||||
web_app: { url: this.webAppUrl }
|
||||
}
|
||||
]]
|
||||
};
|
||||
|
||||
await this.bot.sendMessage(chatId,
|
||||
'🎵 Добро пожаловать в Quixotic!\n\n' +
|
||||
'Добро пожаловать в Quixotic!\n\n' +
|
||||
'Найди любую песню на SoundCloud и получи MP3 файл прямо в чат.\n\n' +
|
||||
'Нажми кнопку ниже, чтобы начать поиск:',
|
||||
{ reply_markup: keyboard }
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('Start command error:', error);
|
||||
await this.bot.sendMessage(chatId, '❌ Произошла ошибка. Попробуйте позже.');
|
||||
logger.error('Start command error:', error);
|
||||
await this.bot.sendMessage(chatId, 'Произошла ошибка. Попробуйте позже.');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -130,13 +131,13 @@ export class QuixoticBot {
|
||||
this.bot.onText(/\/help/, async (msg: Message) => {
|
||||
const chatId = msg.chat.id;
|
||||
|
||||
const helpText = `🎵 *Quixotic - SoundCloud to MP3*
|
||||
const helpText = `*Quixotic - SoundCloud to MP3*
|
||||
|
||||
*Как пользоваться:*
|
||||
1️⃣ Нажми кнопку "Открыть Quixotic"
|
||||
2️⃣ Введи название песни в поисковую строку
|
||||
3️⃣ Выбери нужный трек из списка
|
||||
4️⃣ Получи MP3 файл в чат!
|
||||
1. Нажми кнопку "Открыть Quixotic"
|
||||
2. Введи название песни в поисковую строку
|
||||
3. Выбери нужный трек из списка
|
||||
4. Получи MP3 файл в чат!
|
||||
|
||||
*Команды:*
|
||||
/start - Запустить приложение
|
||||
@@ -144,10 +145,10 @@ export class QuixoticBot {
|
||||
/history - История поиска
|
||||
|
||||
*Возможности:*
|
||||
✅ Поиск по SoundCloud
|
||||
✅ Высокое качество MP3 (192kbps)
|
||||
✅ Быстрая конвертация
|
||||
✅ История поиска`;
|
||||
- Поиск по SoundCloud
|
||||
- Высокое качество MP3 (192kbps)
|
||||
- Быстрая конвертация
|
||||
- История поиска`;
|
||||
|
||||
await this.bot.sendMessage(chatId, helpText, { parse_mode: 'Markdown' });
|
||||
});
|
||||
@@ -174,7 +175,7 @@ export class QuixoticBot {
|
||||
return;
|
||||
}
|
||||
|
||||
let historyText = '📋 *Последние поисковые запросы:*\n\n';
|
||||
let historyText = '*Последние поисковые запросы:*\n\n';
|
||||
history.forEach((item, index) => {
|
||||
const date = new Date(item.created_at).toLocaleDateString('ru-RU');
|
||||
historyText += `${index + 1}. ${item.query} _(${date})_\n`;
|
||||
@@ -182,8 +183,8 @@ export class QuixoticBot {
|
||||
|
||||
await this.bot.sendMessage(chatId, historyText, { parse_mode: 'Markdown' });
|
||||
} catch (error) {
|
||||
console.error('History command error:', error);
|
||||
await this.bot.sendMessage(chatId, '❌ Ошибка получения истории.');
|
||||
logger.error('History command error:', error);
|
||||
await this.bot.sendMessage(chatId, 'Ошибка получения истории.');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -226,10 +227,10 @@ export class QuixoticBot {
|
||||
type: 'article',
|
||||
id: `${index}`,
|
||||
title: video.title,
|
||||
description: `${video.channel} • ${this.formatDuration(video.duration)}`,
|
||||
description: `${video.channel} - ${this.formatDuration(video.duration)}`,
|
||||
thumb_url: video.thumbnail,
|
||||
input_message_content: {
|
||||
message_text: `🎵 ${video.title}\n🔗 ${video.url}`
|
||||
message_text: `${video.title}\n${video.url}`
|
||||
}
|
||||
}));
|
||||
|
||||
@@ -238,31 +239,31 @@ export class QuixoticBot {
|
||||
is_personal: true
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Inline query error:', error);
|
||||
logger.error('Inline query error:', error);
|
||||
await this.bot.answerInlineQuery(queryId, []);
|
||||
}
|
||||
});
|
||||
|
||||
// Error handler with detailed logging
|
||||
this.bot.on('error', (error: any) => {
|
||||
console.error('🚨 Telegram bot error:', error.message || error);
|
||||
console.error('Error code:', error.code);
|
||||
console.error('Full error:', error);
|
||||
logger.error('Telegram bot error:', error.message || error);
|
||||
logger.error('Error code:', error.code);
|
||||
logger.error('Full error:', error);
|
||||
});
|
||||
|
||||
// Handle polling errors specifically
|
||||
this.bot.on('polling_error', (error: any) => {
|
||||
console.error('🚨 Telegram polling error:', error.message || error);
|
||||
console.error('Error code:', error.code);
|
||||
logger.error('Telegram polling error:', error.message || error);
|
||||
logger.error('Error code:', error.code);
|
||||
|
||||
// Don't crash on polling errors, just log them
|
||||
if (error.code === 'ETELEGRAM') {
|
||||
console.warn('⚠️ Telegram API error - continuing operation');
|
||||
logger.warn('Telegram API error - continuing operation');
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
console.log('✅ Bot handlers setup complete');
|
||||
logger.telegram('Bot handlers setup complete');
|
||||
}
|
||||
|
||||
private async getSearchHistory(userId: number): Promise<SearchResult[]> {
|
||||
@@ -276,8 +277,9 @@ export class QuixoticBot {
|
||||
|
||||
private async sendAudioFileInternal(chatId: number, audioUrlOrPath: string, title: string, performer?: string, thumbnail?: string): Promise<void> {
|
||||
try {
|
||||
console.log(`📤 Sending: ${title} to chat ${chatId}`);
|
||||
console.log(`📂 File source: ${audioUrlOrPath}`);
|
||||
logger.telegram('Sending audio', `${title} to chat ${chatId}`);
|
||||
logger.debug(`File source: ${audioUrlOrPath}`);
|
||||
logger.debug(`Thumbnail: ${thumbnail}`);
|
||||
|
||||
// Check if it's a URL or local file path
|
||||
const isUrl = audioUrlOrPath.startsWith('http');
|
||||
@@ -288,82 +290,132 @@ export class QuixoticBot {
|
||||
const urlParts = audioUrlOrPath.split('/');
|
||||
const filename = urlParts[urlParts.length - 1];
|
||||
filePath = require('path').join(process.cwd(), 'downloads', filename);
|
||||
console.log(`📂 Converted URL to local path: ${filePath}`);
|
||||
logger.debug(`Converted URL to local path: ${filePath}`);
|
||||
}
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const https = require('https');
|
||||
|
||||
// Check if file exists
|
||||
if (!fs.existsSync(filePath)) {
|
||||
console.error(`❌ File not found: ${filePath}`);
|
||||
logger.error(`File not found: ${filePath}`);
|
||||
throw new Error('File not found: ' + filePath);
|
||||
}
|
||||
|
||||
// Get file stats for debugging
|
||||
const stats = fs.statSync(filePath);
|
||||
console.log(`📊 File size: ${(stats.size / 1024 / 1024).toFixed(2)} MB`);
|
||||
logger.debug(`File size: ${(stats.size / 1024 / 1024).toFixed(2)} MB`);
|
||||
|
||||
// Generate custom filename for display
|
||||
const safeTitle = (title || 'audio').replace(/[^\w\s-]/g, '').replace(/\s+/g, '_').substring(0, 30);
|
||||
const safePerformer = (performer || '').replace(/[^\w\s-]/g, '').replace(/\s+/g, '_').substring(0, 20);
|
||||
const customFilename = safePerformer ? `${safePerformer} - ${safeTitle}.mp3` : `${safeTitle}.mp3`;
|
||||
|
||||
console.log(`📝 Sending as: ${customFilename}`);
|
||||
logger.debug(`Sending as: ${customFilename}`);
|
||||
|
||||
// Download thumbnail if provided
|
||||
let thumbnailPath: string | undefined;
|
||||
if (thumbnail && thumbnail.startsWith('http')) {
|
||||
try {
|
||||
logger.debug('Downloading thumbnail...');
|
||||
const thumbnailFilename = `thumb_${Date.now()}.jpg`;
|
||||
thumbnailPath = path.join(process.cwd(), 'downloads', thumbnailFilename);
|
||||
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
const file = fs.createWriteStream(thumbnailPath);
|
||||
https.get(thumbnail, (response: any) => {
|
||||
response.pipe(file);
|
||||
file.on('finish', () => {
|
||||
file.close();
|
||||
resolve();
|
||||
});
|
||||
}).on('error', (err: any) => {
|
||||
fs.unlink(thumbnailPath, () => {});
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
|
||||
logger.success(`Thumbnail downloaded: ${thumbnailPath}`);
|
||||
} catch (thumbError: any) {
|
||||
logger.warn('Failed to download thumbnail:', thumbError.message);
|
||||
thumbnailPath = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
// Send file using stream (better for large files)
|
||||
const fileStream = fs.createReadStream(filePath);
|
||||
|
||||
// Try sending as audio with metadata
|
||||
try {
|
||||
await this.bot.sendAudio(chatId, fileStream, {
|
||||
const options: any = {
|
||||
title: title,
|
||||
performer: performer || 'Unknown Artist',
|
||||
caption: undefined,
|
||||
thumbnail: undefined, // Thumbnail requires special handling
|
||||
parse_mode: undefined
|
||||
}, {
|
||||
};
|
||||
|
||||
// Add thumbnail if downloaded
|
||||
if (thumbnailPath) {
|
||||
options.thumbnail = fs.createReadStream(thumbnailPath);
|
||||
}
|
||||
|
||||
await this.bot.sendAudio(chatId, fileStream, options, {
|
||||
filename: customFilename,
|
||||
contentType: 'audio/mpeg'
|
||||
});
|
||||
console.log(`✅ Audio sent successfully: ${title}`);
|
||||
|
||||
logger.success(`Audio sent successfully: ${title}`);
|
||||
|
||||
// Clean up thumbnail file
|
||||
if (thumbnailPath) {
|
||||
fs.unlink(thumbnailPath, (err: any) => {
|
||||
if (err) logger.error('Failed to delete thumbnail:', err);
|
||||
});
|
||||
}
|
||||
return;
|
||||
|
||||
} catch (error: any) {
|
||||
console.error('❌ Audio send failed:', error.message);
|
||||
console.error('Error code:', error.code);
|
||||
logger.error('Audio send failed:', error.message);
|
||||
logger.error('Error code:', error.code);
|
||||
|
||||
// Clean up thumbnail file on error
|
||||
if (thumbnailPath) {
|
||||
fs.unlink(thumbnailPath, () => {});
|
||||
}
|
||||
|
||||
// Fallback: try as document
|
||||
try {
|
||||
console.log('🔄 Retrying as document...');
|
||||
logger.info('Retrying as document...');
|
||||
const docStream = fs.createReadStream(filePath);
|
||||
|
||||
await this.bot.sendDocument(chatId, docStream, {
|
||||
caption: `🎵 ${title}\n👤 ${performer || 'Unknown Artist'}`,
|
||||
caption: `${title}\n${performer || 'Unknown Artist'}`,
|
||||
parse_mode: undefined
|
||||
}, {
|
||||
filename: customFilename,
|
||||
contentType: 'audio/mpeg'
|
||||
});
|
||||
console.log(`✅ Document sent successfully: ${title}`);
|
||||
logger.success(`Document sent successfully: ${title}`);
|
||||
return;
|
||||
|
||||
} catch (documentError: any) {
|
||||
console.error('❌ Document send also failed:', documentError.message);
|
||||
logger.error('Document send also failed:', documentError.message);
|
||||
throw documentError;
|
||||
}
|
||||
}
|
||||
|
||||
} catch (error: any) {
|
||||
console.error('❌ Send failed completely:', error.message);
|
||||
console.error('Full error:', error);
|
||||
logger.error('Send failed completely:', error.message);
|
||||
logger.error('Full error:', error);
|
||||
|
||||
// Send error message to user
|
||||
try {
|
||||
await this.bot.sendMessage(chatId,
|
||||
`❌ Не удалось отправить файл.\n🎵 ${title}\n\nПопробуйте другой трек.`
|
||||
`Не удалось отправить файл.\n${title}\n\nПопробуйте другой трек.`
|
||||
);
|
||||
} catch {
|
||||
console.error('❌ Could not even send error message');
|
||||
logger.error('Could not even send error message');
|
||||
}
|
||||
|
||||
// Re-throw to trigger unhandled rejection handler
|
||||
@@ -382,11 +434,11 @@ export class QuixoticBot {
|
||||
const data: WebAppData = JSON.parse(msg.web_app.data);
|
||||
|
||||
if (data.action === 'send_audio') {
|
||||
console.log(`🎵 WebApp request: ${data.title}`);
|
||||
logger.telegram('WebApp request', data.title);
|
||||
await this.sendAudioFileInternal(chatId, data.audioUrl, data.title);
|
||||
}
|
||||
} catch (parseError: any) {
|
||||
console.error('❌ WebApp data parse error:', parseError.message);
|
||||
logger.error('WebApp data parse error:', parseError.message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -405,7 +457,7 @@ if (require.main === module) {
|
||||
const webAppUrl = process.env.WEB_APP_URL || 'https://your-domain.com';
|
||||
|
||||
if (!token) {
|
||||
console.error('❌ TELEGRAM_BOT_TOKEN environment variable is required');
|
||||
logger.error('TELEGRAM_BOT_TOKEN environment variable is required');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user