faster smaller
This commit is contained in:
@@ -13,8 +13,8 @@ RUN yarn install --frozen-lockfile && yarn cache clean
|
||||
# Copy source code
|
||||
COPY . .
|
||||
|
||||
# Build the application
|
||||
RUN yarn build
|
||||
# Build the application with minification
|
||||
RUN yarn build:prod
|
||||
|
||||
# Clean dev dependencies
|
||||
RUN yarn install --production --frozen-lockfile
|
||||
@@ -56,4 +56,4 @@ HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
|
||||
CMD node -e "require('http').get('http://localhost:3000/health', (res) => { process.exit(res.statusCode === 200 ? 0 : 1) })"
|
||||
|
||||
# Start the application
|
||||
CMD ["node", "dist/server.js"]
|
||||
CMD ["node", "dist/server.js"]
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
"build": "tsc && tsc -p tsconfig.frontend.json",
|
||||
"build:backend": "tsc",
|
||||
"build:frontend": "tsc -p tsconfig.frontend.json",
|
||||
"build:prod": "yarn build && node scripts/minify.js",
|
||||
"minify": "node scripts/minify.js",
|
||||
"start": "node dist/server.js",
|
||||
"dev": "ts-node src/server.ts",
|
||||
"dev:watch": "nodemon --exec ts-node src/server.ts",
|
||||
@@ -46,7 +48,9 @@
|
||||
"@typescript-eslint/eslint-plugin": "^8.41.0",
|
||||
"@typescript-eslint/parser": "^8.41.0",
|
||||
"eslint": "^9.34.0",
|
||||
"html-minifier-terser": "^7.2.0",
|
||||
"nodemon": "^3.0.2",
|
||||
"terser": "^5.44.1",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.9.2"
|
||||
},
|
||||
|
||||
1
public/index.min.html
Normal file
1
public/index.min.html
Normal file
File diff suppressed because one or more lines are too long
@@ -1,12 +1,6 @@
|
||||
# Allow search engines
|
||||
User-agent: Googlebot
|
||||
Allow: /
|
||||
|
||||
User-agent: Bingbot
|
||||
Allow: /
|
||||
|
||||
User-agent: Yandexbot
|
||||
Allow: /
|
||||
|
||||
# Allow all search engines and bots
|
||||
User-agent: *
|
||||
Disallow: /
|
||||
Allow: /
|
||||
|
||||
# Sitemap
|
||||
Sitemap: https://music.quixy.uk/sitemap.xml
|
||||
|
||||
@@ -46,6 +46,8 @@ class QuixoticApp {
|
||||
private welcomePlaceholder!: HTMLElement;
|
||||
private searchTimeout?: NodeJS.Timeout;
|
||||
private currentVideos: VideoResult[] = [];
|
||||
private loadingStartTime: number = 0;
|
||||
private minLoadingDuration: number = 400; // Минимальное время показа спиннера (ms)
|
||||
|
||||
constructor() {
|
||||
this.tg = (window as WindowWithTelegram).Telegram?.WebApp;
|
||||
@@ -91,10 +93,13 @@ class QuixoticApp {
|
||||
return;
|
||||
}
|
||||
|
||||
// Set new timeout for search (500ms delay)
|
||||
// Show loading spinner immediately for better UX feedback
|
||||
this.showLoading();
|
||||
|
||||
// Set new timeout for search (300ms delay - best practice for instant search)
|
||||
this.searchTimeout = setTimeout(() => {
|
||||
this.search();
|
||||
}, 500);
|
||||
}, 300);
|
||||
});
|
||||
|
||||
// Still handle Enter key for immediate search
|
||||
@@ -148,14 +153,35 @@ class QuixoticApp {
|
||||
}
|
||||
|
||||
const data: SearchResponse = await response.json();
|
||||
|
||||
// Ensure minimum loading time for better UX (prevents flashing)
|
||||
const elapsedTime = Date.now() - this.loadingStartTime;
|
||||
const remainingTime = Math.max(0, this.minLoadingDuration - elapsedTime);
|
||||
|
||||
if (remainingTime > 0) {
|
||||
await new Promise(resolve => setTimeout(resolve, remainingTime));
|
||||
}
|
||||
|
||||
this.displayResults(data.videos);
|
||||
} catch (error) {
|
||||
console.error('Search error:', error);
|
||||
|
||||
// Ensure minimum loading time even for errors
|
||||
const elapsedTime = Date.now() - this.loadingStartTime;
|
||||
const remainingTime = Math.max(0, this.minLoadingDuration - elapsedTime);
|
||||
|
||||
if (remainingTime > 0) {
|
||||
await new Promise(resolve => setTimeout(resolve, remainingTime));
|
||||
}
|
||||
|
||||
this.showNoResults();
|
||||
}
|
||||
}
|
||||
|
||||
private showLoading(): void {
|
||||
// Record loading start time
|
||||
this.loadingStartTime = Date.now();
|
||||
|
||||
// Clear any existing status messages
|
||||
const existingMessage = document.querySelector('.tg-status-message');
|
||||
if (existingMessage) {
|
||||
@@ -201,8 +227,15 @@ class QuixoticApp {
|
||||
this.noResults.classList.add('tg-hidden');
|
||||
this.noResults.style.display = 'none';
|
||||
|
||||
this.results.innerHTML = videos.map(video => `
|
||||
<div class='tg-list-item' onclick='app.convertVideo("${video.id}", "${this.escapeHtml(video.title)}", "${this.escapeHtml(video.url)}")'>
|
||||
// Use DocumentFragment for better performance
|
||||
const fragment = document.createDocumentFragment();
|
||||
|
||||
videos.forEach(video => {
|
||||
const item = document.createElement('div');
|
||||
item.className = 'tg-list-item';
|
||||
item.dataset.videoId = video.id;
|
||||
|
||||
item.innerHTML = `
|
||||
<div class='tg-list-item__content'>
|
||||
<div class='tg-list-item__media'>
|
||||
<img class='tg-list-item__thumbnail'
|
||||
@@ -217,29 +250,35 @@ class QuixoticApp {
|
||||
<div class='tg-list-item__subtitle'>${this.escapeHtml(video.channel)}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
|
||||
// Add touch event listeners to prevent sticky states
|
||||
this.results.querySelectorAll('.tg-list-item').forEach(item => {
|
||||
const element = item as HTMLElement;
|
||||
`;
|
||||
|
||||
// Use event listener instead of inline onclick
|
||||
item.addEventListener('click', () => {
|
||||
this.convertVideo(video.id, video.title, video.url);
|
||||
});
|
||||
|
||||
// Reset visual state on touch end
|
||||
element.addEventListener('touchend', () => {
|
||||
item.addEventListener('touchend', () => {
|
||||
setTimeout(() => {
|
||||
element.blur();
|
||||
element.style.background = '';
|
||||
element.style.transform = '';
|
||||
item.blur();
|
||||
item.style.background = '';
|
||||
item.style.transform = '';
|
||||
}, 100);
|
||||
});
|
||||
}, { passive: true });
|
||||
|
||||
// Also handle mouse leave for desktop
|
||||
element.addEventListener('mouseleave', () => {
|
||||
element.style.background = '';
|
||||
element.style.transform = '';
|
||||
item.addEventListener('mouseleave', () => {
|
||||
item.style.background = '';
|
||||
item.style.transform = '';
|
||||
});
|
||||
|
||||
fragment.appendChild(item);
|
||||
});
|
||||
|
||||
// Clear and append once
|
||||
this.results.innerHTML = '';
|
||||
this.results.appendChild(fragment);
|
||||
|
||||
// Lazy load images with IntersectionObserver
|
||||
const imageObserver = new IntersectionObserver((entries) => {
|
||||
entries.forEach(entry => {
|
||||
@@ -281,8 +320,8 @@ class QuixoticApp {
|
||||
const performer = video?.channel || 'Unknown Artist';
|
||||
const thumbnail = video?.thumbnail || '';
|
||||
|
||||
// Find the clicked element by looking for the one that contains this videoId
|
||||
const videoElement = document.querySelector(`[onclick*="${videoId}"]`) as HTMLElement;
|
||||
// Find the clicked element using data attribute
|
||||
const videoElement = document.querySelector(`[data-video-id="${videoId}"]`) as HTMLElement;
|
||||
if (videoElement) {
|
||||
// Force blur to reset any active/focus states
|
||||
videoElement.blur();
|
||||
|
||||
10
public/sitemap.xml
Normal file
10
public/sitemap.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
|
||||
xmlns:xhtml="http://www.w3.org/1999/xhtml">
|
||||
<url>
|
||||
<loc>https://music.quixy.uk/</loc>
|
||||
<lastmod>2025-11-07</lastmod>
|
||||
<changefreq>daily</changefreq>
|
||||
<priority>1.0</priority>
|
||||
</url>
|
||||
</urlset>
|
||||
@@ -203,25 +203,29 @@ body {
|
||||
padding: var(--tg-spacing-xxl);
|
||||
display: none;
|
||||
z-index: 10;
|
||||
opacity: 0;
|
||||
transition: opacity 0.15s ease-in;
|
||||
}
|
||||
|
||||
.tg-spinner.tg-spinner--visible {
|
||||
display: block;
|
||||
animation: tg-fade-in 0.15s ease-in forwards;
|
||||
}
|
||||
|
||||
.tg-spinner__icon {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border: 2px solid var(--tg-color-secondary-bg);
|
||||
border-top: 2px solid var(--tg-color-button);
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 3px solid var(--tg-color-secondary-bg);
|
||||
border-top: 3px solid var(--tg-color-button);
|
||||
border-radius: 50%;
|
||||
margin: 0 auto var(--tg-spacing-lg);
|
||||
animation: tg-spin 1s linear infinite;
|
||||
animation: tg-spin 0.8s cubic-bezier(0.4, 0, 0.2, 1) infinite;
|
||||
}
|
||||
|
||||
.tg-spinner__text {
|
||||
font-size: var(--tg-font-size-sm);
|
||||
color: var(--tg-color-hint);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
@keyframes tg-spin {
|
||||
@@ -229,6 +233,17 @@ body {
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
@keyframes tg-fade-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translate(-50%, -45%);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
}
|
||||
|
||||
/* List component */
|
||||
.tg-list {
|
||||
display: none;
|
||||
|
||||
127
scripts/minify.js
Executable file
127
scripts/minify.js
Executable file
@@ -0,0 +1,127 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { minify } = require('terser');
|
||||
const { minify: minifyHtml } = require('html-minifier-terser');
|
||||
|
||||
const publicDir = path.join(__dirname, '..', 'public');
|
||||
const distDir = path.join(publicDir, 'dist');
|
||||
|
||||
async function minifyJavaScript() {
|
||||
console.log('🔧 Minifying JavaScript...');
|
||||
|
||||
const jsFile = path.join(distDir, 'script.js');
|
||||
|
||||
if (!fs.existsSync(jsFile)) {
|
||||
console.error('❌ script.js not found. Run build first.');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const code = fs.readFileSync(jsFile, 'utf8');
|
||||
|
||||
const result = await minify(code, {
|
||||
compress: {
|
||||
dead_code: true,
|
||||
drop_console: false, // Keep console for debugging
|
||||
drop_debugger: true,
|
||||
keep_classnames: true,
|
||||
keep_fnames: false,
|
||||
passes: 2
|
||||
},
|
||||
mangle: {
|
||||
keep_classnames: true,
|
||||
keep_fnames: false
|
||||
},
|
||||
format: {
|
||||
comments: false
|
||||
},
|
||||
sourceMap: {
|
||||
filename: 'script.js',
|
||||
url: 'script.js.map'
|
||||
}
|
||||
});
|
||||
|
||||
if (result.code) {
|
||||
fs.writeFileSync(jsFile, result.code);
|
||||
if (result.map) {
|
||||
fs.writeFileSync(jsFile + '.map', result.map);
|
||||
}
|
||||
|
||||
const originalSize = Buffer.byteLength(code, 'utf8');
|
||||
const minifiedSize = Buffer.byteLength(result.code, 'utf8');
|
||||
const savings = ((1 - minifiedSize / originalSize) * 100).toFixed(1);
|
||||
|
||||
console.log(`✅ JavaScript minified: ${originalSize} → ${minifiedSize} bytes (${savings}% reduction)`);
|
||||
} else {
|
||||
console.error('❌ Minification failed');
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
async function minifyHTML() {
|
||||
console.log('🔧 Minifying HTML...');
|
||||
|
||||
const htmlFile = path.join(publicDir, 'index.html');
|
||||
|
||||
if (!fs.existsSync(htmlFile)) {
|
||||
console.error('❌ index.html not found');
|
||||
return;
|
||||
}
|
||||
|
||||
const html = fs.readFileSync(htmlFile, 'utf8');
|
||||
|
||||
const minified = await minifyHtml(html, {
|
||||
collapseWhitespace: true,
|
||||
removeComments: true,
|
||||
removeRedundantAttributes: true,
|
||||
removeScriptTypeAttributes: true,
|
||||
removeStyleLinkTypeAttributes: true,
|
||||
useShortDoctype: true,
|
||||
minifyCSS: true,
|
||||
minifyJS: false, // Don't minify inline JS (we handle it separately)
|
||||
keepClosingSlash: true
|
||||
});
|
||||
|
||||
const originalSize = Buffer.byteLength(html, 'utf8');
|
||||
const minifiedSize = Buffer.byteLength(minified, 'utf8');
|
||||
const savings = ((1 - minifiedSize / originalSize) * 100).toFixed(1);
|
||||
|
||||
// Save to dist folder
|
||||
fs.writeFileSync(path.join(publicDir, 'index.min.html'), minified);
|
||||
|
||||
console.log(`✅ HTML minified: ${originalSize} → ${minifiedSize} bytes (${savings}% reduction)`);
|
||||
}
|
||||
|
||||
async function minifyCSS() {
|
||||
console.log('🔧 Checking CSS...');
|
||||
|
||||
const cssFile = path.join(publicDir, 'style.css');
|
||||
|
||||
if (!fs.existsSync(cssFile)) {
|
||||
console.log('ℹ️ No CSS file to minify');
|
||||
return;
|
||||
}
|
||||
|
||||
const css = fs.readFileSync(cssFile, 'utf8');
|
||||
const originalSize = Buffer.byteLength(css, 'utf8');
|
||||
|
||||
console.log(`ℹ️ CSS size: ${originalSize} bytes (already optimized)`);
|
||||
}
|
||||
|
||||
async function main() {
|
||||
console.log('🚀 Starting minification process...\n');
|
||||
|
||||
try {
|
||||
await minifyJavaScript();
|
||||
await minifyHTML();
|
||||
await minifyCSS();
|
||||
|
||||
console.log('\n✨ All files minified successfully!');
|
||||
} catch (error) {
|
||||
console.error('❌ Minification error:', error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
99
src/bot.ts
99
src/bot.ts
@@ -40,7 +40,27 @@ export class QuixoticBot {
|
||||
private db: Database;
|
||||
|
||||
constructor(token: string, webAppUrl: string) {
|
||||
this.bot = new TelegramBot(token, { polling: true });
|
||||
// Validate token format
|
||||
if (!token || token.length < 40 || token === 'your_telegram_bot_token_here') {
|
||||
throw new Error('Invalid or placeholder TELEGRAM_BOT_TOKEN provided');
|
||||
}
|
||||
|
||||
// Use webhook in production, polling in development
|
||||
const useWebhook = process.env.NODE_ENV === 'production' && process.env.WEBHOOK_URL;
|
||||
|
||||
if (useWebhook) {
|
||||
console.log('🌐 Using webhook mode for production');
|
||||
this.bot = new TelegramBot(token, {
|
||||
webHook: {
|
||||
port: 8443,
|
||||
host: '0.0.0.0'
|
||||
}
|
||||
});
|
||||
} else {
|
||||
console.log('🔄 Using polling mode for development');
|
||||
this.bot = new TelegramBot(token, { polling: true });
|
||||
}
|
||||
|
||||
this.webAppUrl = webAppUrl;
|
||||
this.db = new Database();
|
||||
this.init();
|
||||
@@ -257,6 +277,7 @@ 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}`);
|
||||
|
||||
// Check if it's a URL or local file path
|
||||
const isUrl = audioUrlOrPath.startsWith('http');
|
||||
@@ -267,68 +288,86 @@ 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}`);
|
||||
}
|
||||
|
||||
// Generate custom filename for display
|
||||
const safeTitle = (title || '').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}` : `${safeTitle}`;
|
||||
const fs = require('fs');
|
||||
|
||||
// Try sending as audio with custom filename
|
||||
// Check if file exists
|
||||
if (!fs.existsSync(filePath)) {
|
||||
console.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`);
|
||||
|
||||
// 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}`);
|
||||
|
||||
// Send file using stream (better for large files)
|
||||
const fileStream = fs.createReadStream(filePath);
|
||||
|
||||
// Try sending as audio with metadata
|
||||
try {
|
||||
const fs = require('fs');
|
||||
|
||||
// Check if file exists
|
||||
if (!fs.existsSync(filePath)) {
|
||||
throw new Error('File not found: ' + filePath);
|
||||
}
|
||||
|
||||
await this.bot.sendAudio(chatId, filePath, {
|
||||
await this.bot.sendAudio(chatId, fileStream, {
|
||||
title: title,
|
||||
performer: performer,
|
||||
performer: performer || 'Unknown Artist',
|
||||
caption: undefined,
|
||||
thumbnail: thumbnail,
|
||||
thumbnail: undefined, // Thumbnail requires special handling
|
||||
parse_mode: undefined
|
||||
}, {
|
||||
filename: customFilename,
|
||||
contentType: 'audio/mpeg'
|
||||
});
|
||||
console.log(`✅ Audio sent: ${title}`);
|
||||
console.log(`✅ Audio sent successfully: ${title}`);
|
||||
return;
|
||||
|
||||
} catch (error: any) {
|
||||
console.log('Audio send failed, trying as document...', error.message);
|
||||
console.error('❌ Audio send failed:', error.message);
|
||||
console.error('Error code:', error.code);
|
||||
|
||||
// Fallback: try as document with custom filename
|
||||
// Fallback: try as document
|
||||
try {
|
||||
await this.bot.sendDocument(chatId, filePath, {
|
||||
caption: undefined,
|
||||
console.log('🔄 Retrying as document...');
|
||||
const docStream = fs.createReadStream(filePath);
|
||||
|
||||
await this.bot.sendDocument(chatId, docStream, {
|
||||
caption: `🎵 ${title}\n👤 ${performer || 'Unknown Artist'}`,
|
||||
parse_mode: undefined
|
||||
}, {
|
||||
filename: customFilename,
|
||||
contentType: 'audio/mpeg'
|
||||
});
|
||||
console.log(`✅ Document sent: ${title}`);
|
||||
console.log(`✅ Document sent successfully: ${title}`);
|
||||
return;
|
||||
|
||||
} catch (documentError: any) {
|
||||
console.error('❌ Document send also failed:', documentError.message);
|
||||
throw documentError;
|
||||
}
|
||||
}
|
||||
|
||||
} catch (error: any) {
|
||||
console.error('❌ Send failed:', error.message);
|
||||
console.error('❌ Send failed completely:', error.message);
|
||||
console.error('Full error:', error);
|
||||
|
||||
// Send fallback with link if it was a URL
|
||||
// Send error message to user
|
||||
try {
|
||||
const message = audioUrlOrPath.startsWith('http')
|
||||
? `❌ Не удалось отправить файл.\n🎵 ${title}\n🔗 ${audioUrlOrPath}`
|
||||
: `❌ Не удалось отправить файл: ${title}`;
|
||||
|
||||
await this.bot.sendMessage(chatId, message);
|
||||
await this.bot.sendMessage(chatId,
|
||||
`❌ Не удалось отправить файл.\n🎵 ${title}\n\nПопробуйте другой трек.`
|
||||
);
|
||||
} catch {
|
||||
// Silent fail
|
||||
console.error('❌ Could not even send error message');
|
||||
}
|
||||
|
||||
// Re-throw to trigger unhandled rejection handler
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -276,8 +276,8 @@ app.get('/health', (req: Request, res: Response) => {
|
||||
});
|
||||
|
||||
// Error handler
|
||||
app.use((_err: Error, _req: Request, res: Response) => {
|
||||
console.error(_err.stack);
|
||||
app.use((err: Error, _req: Request, res: Response, _next: any) => {
|
||||
console.error(err.stack);
|
||||
res.status(500).json({ error: 'Something went wrong!' });
|
||||
});
|
||||
|
||||
@@ -316,7 +316,7 @@ app.listen(port, () => {
|
||||
const botToken = process.env.TELEGRAM_BOT_TOKEN;
|
||||
const webAppUrl = process.env.WEB_APP_URL || `http://localhost:${port}`;
|
||||
|
||||
if (botToken && botToken.length > 10) {
|
||||
if (botToken && botToken.length > 10 && botToken !== 'your_telegram_bot_token_here') {
|
||||
try {
|
||||
const botInstance = new QuixoticBot(botToken, webAppUrl);
|
||||
// Store bot instance globally for API access
|
||||
@@ -324,10 +324,31 @@ if (botToken && botToken.length > 10) {
|
||||
console.log('🤖 Telegram bot started and stored globally');
|
||||
} catch (error: any) {
|
||||
console.error('❌ Bot initialization failed:', error.message);
|
||||
console.warn('⚠️ Bot disabled due to error');
|
||||
console.warn('⚠️ Bot disabled due to error');
|
||||
console.warn('⚠️ Telegram integration will not be available');
|
||||
// Don't crash the server, continue without bot
|
||||
}
|
||||
} else {
|
||||
console.warn('⚠️ TELEGRAM_BOT_TOKEN not found or invalid - bot will not start');
|
||||
console.warn('⚠️ TELEGRAM_BOT_TOKEN not configured properly');
|
||||
console.warn('⚠️ Bot will not start - only web interface will be available');
|
||||
console.warn('ℹ️ To enable Telegram bot, set a valid TELEGRAM_BOT_TOKEN');
|
||||
}
|
||||
|
||||
// Handle unhandled promise rejections
|
||||
process.on('unhandledRejection', (reason: any, promise: Promise<any>) => {
|
||||
console.error('🚨 Unhandled Rejection at:', promise);
|
||||
console.error('Reason:', reason);
|
||||
|
||||
// Log but don't crash the server
|
||||
if (reason?.code === 'ETELEGRAM') {
|
||||
console.warn('⚠️ Telegram API error - continuing operation');
|
||||
}
|
||||
});
|
||||
|
||||
// Handle uncaught exceptions
|
||||
process.on('uncaughtException', (error: Error) => {
|
||||
console.error('🚨 Uncaught Exception:', error);
|
||||
// Log but try to continue
|
||||
});
|
||||
|
||||
export default app;
|
||||
|
||||
148
yarn.lock
148
yarn.lock
@@ -142,12 +142,28 @@
|
||||
resolved "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz"
|
||||
integrity sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==
|
||||
|
||||
"@jridgewell/resolve-uri@^3.0.3":
|
||||
"@jridgewell/gen-mapping@^0.3.5":
|
||||
version "0.3.13"
|
||||
resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz#6342a19f44347518c93e43b1ac69deb3c4656a1f"
|
||||
integrity sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==
|
||||
dependencies:
|
||||
"@jridgewell/sourcemap-codec" "^1.5.0"
|
||||
"@jridgewell/trace-mapping" "^0.3.24"
|
||||
|
||||
"@jridgewell/resolve-uri@^3.0.3", "@jridgewell/resolve-uri@^3.1.0":
|
||||
version "3.1.2"
|
||||
resolved "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz"
|
||||
integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==
|
||||
|
||||
"@jridgewell/sourcemap-codec@^1.4.10":
|
||||
"@jridgewell/source-map@^0.3.3":
|
||||
version "0.3.11"
|
||||
resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.11.tgz#b21835cbd36db656b857c2ad02ebd413cc13a9ba"
|
||||
integrity sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==
|
||||
dependencies:
|
||||
"@jridgewell/gen-mapping" "^0.3.5"
|
||||
"@jridgewell/trace-mapping" "^0.3.25"
|
||||
|
||||
"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.5.0":
|
||||
version "1.5.5"
|
||||
resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz"
|
||||
integrity sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==
|
||||
@@ -160,6 +176,14 @@
|
||||
"@jridgewell/resolve-uri" "^3.0.3"
|
||||
"@jridgewell/sourcemap-codec" "^1.4.10"
|
||||
|
||||
"@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25":
|
||||
version "0.3.31"
|
||||
resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz#db15d6781c931f3a251a3dac39501c98a6082fd0"
|
||||
integrity sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==
|
||||
dependencies:
|
||||
"@jridgewell/resolve-uri" "^3.1.0"
|
||||
"@jridgewell/sourcemap-codec" "^1.4.14"
|
||||
|
||||
"@nodelib/fs.scandir@2.1.5":
|
||||
version "2.1.5"
|
||||
resolved "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz"
|
||||
@@ -686,6 +710,11 @@ braces@^3.0.3, braces@~3.0.2:
|
||||
dependencies:
|
||||
fill-range "^7.1.1"
|
||||
|
||||
buffer-from@^1.0.0:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5"
|
||||
integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==
|
||||
|
||||
bytes@3.1.2:
|
||||
version "3.1.2"
|
||||
resolved "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz"
|
||||
@@ -722,6 +751,14 @@ callsites@^3.0.0:
|
||||
resolved "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz"
|
||||
integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==
|
||||
|
||||
camel-case@^4.1.2:
|
||||
version "4.1.2"
|
||||
resolved "https://registry.yarnpkg.com/camel-case/-/camel-case-4.1.2.tgz#9728072a954f805228225a6deea6b38461e1bd5a"
|
||||
integrity sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==
|
||||
dependencies:
|
||||
pascal-case "^3.1.2"
|
||||
tslib "^2.0.3"
|
||||
|
||||
caseless@~0.12.0:
|
||||
version "0.12.0"
|
||||
resolved "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz"
|
||||
@@ -750,6 +787,13 @@ chokidar@^3.5.2:
|
||||
optionalDependencies:
|
||||
fsevents "~2.3.2"
|
||||
|
||||
clean-css@~5.3.2:
|
||||
version "5.3.3"
|
||||
resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-5.3.3.tgz#b330653cd3bd6b75009cc25c714cae7b93351ccd"
|
||||
integrity sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg==
|
||||
dependencies:
|
||||
source-map "~0.6.0"
|
||||
|
||||
color-convert@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz"
|
||||
@@ -769,6 +813,16 @@ combined-stream@^1.0.8, combined-stream@~1.0.6:
|
||||
dependencies:
|
||||
delayed-stream "~1.0.0"
|
||||
|
||||
commander@^10.0.0:
|
||||
version "10.0.1"
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-10.0.1.tgz#881ee46b4f77d1c1dccc5823433aa39b022cbe06"
|
||||
integrity sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==
|
||||
|
||||
commander@^2.20.0:
|
||||
version "2.20.3"
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
|
||||
integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
|
||||
|
||||
compressible@~2.0.18:
|
||||
version "2.0.18"
|
||||
resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.18.tgz#af53cca6b070d4c3c0750fbd77286a6d7cc46fba"
|
||||
@@ -938,6 +992,14 @@ diff@^4.0.1:
|
||||
resolved "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz"
|
||||
integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==
|
||||
|
||||
dot-case@^3.0.4:
|
||||
version "3.0.4"
|
||||
resolved "https://registry.yarnpkg.com/dot-case/-/dot-case-3.0.4.tgz#9b2b670d00a431667a8a75ba29cd1b98809ce751"
|
||||
integrity sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==
|
||||
dependencies:
|
||||
no-case "^3.0.4"
|
||||
tslib "^2.0.3"
|
||||
|
||||
dotenv@^8.2.0:
|
||||
version "8.6.0"
|
||||
resolved "https://registry.npmjs.org/dotenv/-/dotenv-8.6.0.tgz"
|
||||
@@ -982,6 +1044,11 @@ end-of-stream@^1.1.0:
|
||||
dependencies:
|
||||
once "^1.4.0"
|
||||
|
||||
entities@^4.4.0:
|
||||
version "4.5.0"
|
||||
resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48"
|
||||
integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==
|
||||
|
||||
es-abstract@^1.23.5, es-abstract@^1.23.6, es-abstract@^1.23.9:
|
||||
version "1.24.0"
|
||||
resolved "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.0.tgz"
|
||||
@@ -1540,6 +1607,19 @@ hasown@^2.0.2:
|
||||
dependencies:
|
||||
function-bind "^1.1.2"
|
||||
|
||||
html-minifier-terser@^7.2.0:
|
||||
version "7.2.0"
|
||||
resolved "https://registry.yarnpkg.com/html-minifier-terser/-/html-minifier-terser-7.2.0.tgz#18752e23a2f0ed4b0f550f217bb41693e975b942"
|
||||
integrity sha512-tXgn3QfqPIpGl9o+K5tpcj3/MN4SfLtsx2GWwBC3SSd0tXQGyF3gsSqad8loJgKZGM3ZxbYDd5yhiBIdWpmvLA==
|
||||
dependencies:
|
||||
camel-case "^4.1.2"
|
||||
clean-css "~5.3.2"
|
||||
commander "^10.0.0"
|
||||
entities "^4.4.0"
|
||||
param-case "^3.0.4"
|
||||
relateurl "^0.2.7"
|
||||
terser "^5.15.1"
|
||||
|
||||
http-errors@2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz"
|
||||
@@ -1900,6 +1980,13 @@ lodash@^4.17.15:
|
||||
resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz"
|
||||
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
|
||||
|
||||
lower-case@^2.0.2:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-2.0.2.tgz#6fa237c63dbdc4a82ca0fd882e4722dc5e634e28"
|
||||
integrity sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==
|
||||
dependencies:
|
||||
tslib "^2.0.3"
|
||||
|
||||
m3u8stream@^0.8.0:
|
||||
version "0.8.6"
|
||||
resolved "https://registry.npmjs.org/m3u8stream/-/m3u8stream-0.8.6.tgz"
|
||||
@@ -2012,6 +2099,14 @@ negotiator@~0.6.4:
|
||||
resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.4.tgz#777948e2452651c570b712dd01c23e262713fff7"
|
||||
integrity sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==
|
||||
|
||||
no-case@^3.0.4:
|
||||
version "3.0.4"
|
||||
resolved "https://registry.yarnpkg.com/no-case/-/no-case-3.0.4.tgz#d361fd5c9800f558551a8369fc0dcd4662b6124d"
|
||||
integrity sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==
|
||||
dependencies:
|
||||
lower-case "^2.0.2"
|
||||
tslib "^2.0.3"
|
||||
|
||||
node-telegram-bot-api@^0.64.0:
|
||||
version "0.64.0"
|
||||
resolved "https://registry.npmjs.org/node-telegram-bot-api/-/node-telegram-bot-api-0.64.0.tgz"
|
||||
@@ -2124,6 +2219,14 @@ p-locate@^5.0.0:
|
||||
dependencies:
|
||||
p-limit "^3.0.2"
|
||||
|
||||
param-case@^3.0.4:
|
||||
version "3.0.4"
|
||||
resolved "https://registry.yarnpkg.com/param-case/-/param-case-3.0.4.tgz#7d17fe4aa12bde34d4a77d91acfb6219caad01c5"
|
||||
integrity sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==
|
||||
dependencies:
|
||||
dot-case "^3.0.4"
|
||||
tslib "^2.0.3"
|
||||
|
||||
parent-module@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz"
|
||||
@@ -2136,6 +2239,14 @@ parseurl@~1.3.3:
|
||||
resolved "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz"
|
||||
integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==
|
||||
|
||||
pascal-case@^3.1.2:
|
||||
version "3.1.2"
|
||||
resolved "https://registry.yarnpkg.com/pascal-case/-/pascal-case-3.1.2.tgz#b48e0ef2b98e205e7c1dae747d0b1508237660eb"
|
||||
integrity sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==
|
||||
dependencies:
|
||||
no-case "^3.0.4"
|
||||
tslib "^2.0.3"
|
||||
|
||||
path-exists@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz"
|
||||
@@ -2377,6 +2488,11 @@ regexp.prototype.flags@^1.5.4:
|
||||
gopd "^1.2.0"
|
||||
set-function-name "^2.0.2"
|
||||
|
||||
relateurl@^0.2.7:
|
||||
version "0.2.7"
|
||||
resolved "https://registry.yarnpkg.com/relateurl/-/relateurl-0.2.7.tgz#54dbf377e51440aca90a4cd274600d3ff2d888a9"
|
||||
integrity sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==
|
||||
|
||||
request-promise-core@1.1.3:
|
||||
version "1.1.3"
|
||||
resolved "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.3.tgz"
|
||||
@@ -2599,6 +2715,19 @@ soundcloud-key-fetch@^1.0.10:
|
||||
resolved "https://registry.npmjs.org/soundcloud-key-fetch/-/soundcloud-key-fetch-1.0.13.tgz"
|
||||
integrity sha512-X1ketDIELpXOj2zuNNlTEtEy8y9Zu5R9m2lzkocXkG0GmUMht2ZypS8mtEDAWtEeqnEqkkLxipOsly+qU1gb6Q==
|
||||
|
||||
source-map-support@~0.5.20:
|
||||
version "0.5.21"
|
||||
resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f"
|
||||
integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==
|
||||
dependencies:
|
||||
buffer-from "^1.0.0"
|
||||
source-map "^0.6.0"
|
||||
|
||||
source-map@^0.6.0, source-map@~0.6.0:
|
||||
version "0.6.1"
|
||||
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
|
||||
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
|
||||
|
||||
split2@^4.1.0:
|
||||
version "4.2.0"
|
||||
resolved "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz"
|
||||
@@ -2695,6 +2824,16 @@ supports-color@^7.1.0:
|
||||
dependencies:
|
||||
has-flag "^4.0.0"
|
||||
|
||||
terser@^5.15.1, terser@^5.44.1:
|
||||
version "5.44.1"
|
||||
resolved "https://registry.yarnpkg.com/terser/-/terser-5.44.1.tgz#e391e92175c299b8c284ad6ded609e37303b0a9c"
|
||||
integrity sha512-t/R3R/n0MSwnnazuPpPNVO60LX0SKL45pyl9YlvxIdkH0Of7D5qM2EVe+yASRIlY5pZ73nclYJfNANGWPwFDZw==
|
||||
dependencies:
|
||||
"@jridgewell/source-map" "^0.3.3"
|
||||
acorn "^8.15.0"
|
||||
commander "^2.20.0"
|
||||
source-map-support "~0.5.20"
|
||||
|
||||
tldts-core@^6.1.86:
|
||||
version "6.1.86"
|
||||
resolved "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.86.tgz"
|
||||
@@ -2765,6 +2904,11 @@ ts-node@^10.9.2:
|
||||
v8-compile-cache-lib "^3.0.1"
|
||||
yn "3.1.1"
|
||||
|
||||
tslib@^2.0.3:
|
||||
version "2.8.1"
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f"
|
||||
integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==
|
||||
|
||||
tunnel-agent@^0.6.0:
|
||||
version "0.6.0"
|
||||
resolved "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz"
|
||||
|
||||
Reference in New Issue
Block a user