This commit is contained in:
Andrey Kondratev
2025-08-27 18:57:09 +05:00
parent 98787a382e
commit 57f0519a32
13 changed files with 829 additions and 550 deletions

275
src/bot.ts Normal file
View File

@@ -0,0 +1,275 @@
import TelegramBot from 'node-telegram-bot-api';
import { Database } from './database';
interface TelegramUser {
id: number;
first_name: string;
last_name?: string;
username?: string;
}
interface Message {
chat: {
id: number;
};
from?: TelegramUser;
web_app?: {
data: string;
};
}
interface InlineQuery {
id: string;
query: string;
}
interface WebAppData {
action: string;
audioUrl: string;
title: string;
}
interface SearchResult {
query: string;
created_at: string;
}
export class QuixoticBot {
private bot: TelegramBot;
private webAppUrl: string;
private db: Database;
constructor(token: string, webAppUrl: string) {
this.bot = new TelegramBot(token, { polling: true });
this.webAppUrl = webAppUrl;
this.db = new Database();
this.init();
}
private init(): void {
console.log('🤖 Telegram bot initialized');
this.setupCommands();
this.setupHandlers();
}
private setupCommands(): void {
// Set bot commands
this.bot.setMyCommands([
{ command: 'start', description: 'Запустить приложение' },
{ command: 'help', description: 'Помощь' },
{ command: 'history', description: 'История поиска' }
]);
}
private setupHandlers(): void {
// Start command
this.bot.onText(/\/start/, async (msg: Message) => {
const chatId = msg.chat.id;
const user = msg.from;
try {
// Add user to database
if (user) {
await this.db.addUser(user);
}
const keyboard = {
inline_keyboard: [[
{
text: '🎵 Открыть Quixotic',
web_app: { url: this.webAppUrl }
}
]]
};
await this.bot.sendMessage(chatId,
'🎵 Добро пожаловать в Quixotic!\n\n' +
'Найди любую песню на YouTube и получи MP3 файл прямо в чат.\n\n' +
'Нажми кнопку ниже, чтобы начать поиск:',
{ reply_markup: keyboard }
);
} catch (error) {
console.error('Start command error:', error);
await this.bot.sendMessage(chatId, '❌ Произошла ошибка. Попробуйте позже.');
}
});
// Help command
this.bot.onText(/\/help/, async (msg: Message) => {
const chatId = msg.chat.id;
const helpText = `🎵 *Quixotic - YouTube to MP3*
*Как пользоваться:*
1⃣ Нажми кнопку "Открыть Quixotic"
2⃣ Введи название песни в поисковую строку
3⃣ Выбери нужный трек из списка
4⃣ Получи MP3 файл в чат!
*Команды:*
/start - Запустить приложение
/help - Эта справка
/history - История поиска
*Возможности:*
✅ Поиск по YouTube
✅ Высокое качество MP3 (128kbps)
✅ Быстрая конвертация
✅ История поиска`;
await this.bot.sendMessage(chatId, helpText, { parse_mode: 'Markdown' });
});
// History command
this.bot.onText(/\/history/, async (msg: Message) => {
const chatId = msg.chat.id;
const userId = msg.from?.id;
if (!userId) return;
try {
const user = await this.db.getUserByTelegramId(userId);
if (!user) {
await this.bot.sendMessage(chatId, 'История поиска пуста.');
return;
}
// Get recent search history
const history = await this.getSearchHistory(user.id);
if (history.length === 0) {
await this.bot.sendMessage(chatId, 'История поиска пуста.');
return;
}
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`;
});
await this.bot.sendMessage(chatId, historyText, { parse_mode: 'Markdown' });
} catch (error) {
console.error('History command error:', error);
await this.bot.sendMessage(chatId, '❌ Ошибка получения истории.');
}
});
// Handle web app data
this.bot.on('web_app_data', async (msg: Message) => {
const chatId = msg.chat.id;
if (!msg.web_app?.data) return;
const data: WebAppData = JSON.parse(msg.web_app.data);
try {
if (data.action === 'send_audio') {
await this.sendAudioFile(chatId, data.audioUrl, data.title);
}
} catch (error) {
console.error('Web app data error:', error);
await this.bot.sendMessage(chatId, '❌ Ошибка обработки данных.');
}
});
// Handle inline queries for search
this.bot.on('inline_query', async (query: InlineQuery) => {
const queryId = query.id;
const searchQuery = query.query;
if (!searchQuery || searchQuery.length < 3) {
await this.bot.answerInlineQuery(queryId, []);
return;
}
try {
const { SoundCloudService } = require('./soundcloud');
const soundcloud = new SoundCloudService();
const videos = await soundcloud.searchTracks(searchQuery, 5);
const results = videos.map((video: any, index: number) => ({
type: 'article',
id: `${index}`,
title: video.title,
description: `${video.channel}${this.formatDuration(video.duration)}`,
thumb_url: video.thumbnail,
input_message_content: {
message_text: `🎵 ${video.title}\n🔗 ${video.url}`
}
}));
await this.bot.answerInlineQuery(queryId, results, {
cache_time: 300,
is_personal: true
});
} catch (error) {
console.error('Inline query error:', error);
await this.bot.answerInlineQuery(queryId, []);
}
});
// Error handler
this.bot.on('error', (error: Error) => {
console.error('Telegram bot error:', error);
});
console.log('✅ Bot handlers setup complete');
}
private async getSearchHistory(userId: number): Promise<SearchResult[]> {
return new Promise((resolve, reject) => {
this.db['db'].all(
`SELECT query, created_at FROM search_history
WHERE user_id = ?
ORDER BY created_at DESC
LIMIT 10`,
[userId],
(err: Error | null, rows: SearchResult[]) => {
if (err) reject(err);
else resolve(rows || []);
}
);
});
}
private async sendAudioFile(chatId: number, audioUrl: string, title: string): Promise<void> {
try {
await this.bot.sendMessage(chatId, '⏳ Подготавливаю MP3 файл...');
// Send audio file
await this.bot.sendAudio(chatId, audioUrl, {
title: title,
performer: 'Quixotic',
caption: `🎵 ${title}`
});
} catch (error) {
console.error('Send audio error:', error);
await this.bot.sendMessage(chatId,
'❌ Не удалось отправить аудиофайл. Попробуйте еще раз.\n\n' +
`Прямая ссылка: ${audioUrl}`
);
}
}
private formatDuration(seconds: number): string {
if (!seconds) return '';
const mins = Math.floor(seconds / 60);
const secs = seconds % 60;
return `${mins}:${secs.toString().padStart(2, '0')}`;
}
}
// Initialize bot if this file is run directly
if (require.main === module) {
const token = process.env.TELEGRAM_BOT_TOKEN;
const webAppUrl = process.env.WEB_APP_URL || 'https://your-domain.com';
if (!token) {
console.error('❌ TELEGRAM_BOT_TOKEN environment variable is required');
process.exit(1);
}
new QuixoticBot(token, webAppUrl);
}