ts
This commit is contained in:
275
src/bot.ts
Normal file
275
src/bot.ts
Normal 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);
|
||||
}
|
||||
Reference in New Issue
Block a user