faster smaller
This commit is contained in:
@@ -13,8 +13,8 @@ RUN yarn install --frozen-lockfile && yarn cache clean
|
|||||||
# Copy source code
|
# Copy source code
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
# Build the application
|
# Build the application with minification
|
||||||
RUN yarn build
|
RUN yarn build:prod
|
||||||
|
|
||||||
# Clean dev dependencies
|
# Clean dev dependencies
|
||||||
RUN yarn install --production --frozen-lockfile
|
RUN yarn install --production --frozen-lockfile
|
||||||
|
|||||||
@@ -7,6 +7,8 @@
|
|||||||
"build": "tsc && tsc -p tsconfig.frontend.json",
|
"build": "tsc && tsc -p tsconfig.frontend.json",
|
||||||
"build:backend": "tsc",
|
"build:backend": "tsc",
|
||||||
"build:frontend": "tsc -p tsconfig.frontend.json",
|
"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",
|
"start": "node dist/server.js",
|
||||||
"dev": "ts-node src/server.ts",
|
"dev": "ts-node src/server.ts",
|
||||||
"dev:watch": "nodemon --exec 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/eslint-plugin": "^8.41.0",
|
||||||
"@typescript-eslint/parser": "^8.41.0",
|
"@typescript-eslint/parser": "^8.41.0",
|
||||||
"eslint": "^9.34.0",
|
"eslint": "^9.34.0",
|
||||||
|
"html-minifier-terser": "^7.2.0",
|
||||||
"nodemon": "^3.0.2",
|
"nodemon": "^3.0.2",
|
||||||
|
"terser": "^5.44.1",
|
||||||
"ts-node": "^10.9.2",
|
"ts-node": "^10.9.2",
|
||||||
"typescript": "^5.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
|
# Allow all search engines and bots
|
||||||
User-agent: Googlebot
|
|
||||||
Allow: /
|
|
||||||
|
|
||||||
User-agent: Bingbot
|
|
||||||
Allow: /
|
|
||||||
|
|
||||||
User-agent: Yandexbot
|
|
||||||
Allow: /
|
|
||||||
|
|
||||||
User-agent: *
|
User-agent: *
|
||||||
Disallow: /
|
Allow: /
|
||||||
|
|
||||||
|
# Sitemap
|
||||||
|
Sitemap: https://music.quixy.uk/sitemap.xml
|
||||||
|
|||||||
@@ -46,6 +46,8 @@ class QuixoticApp {
|
|||||||
private welcomePlaceholder!: HTMLElement;
|
private welcomePlaceholder!: HTMLElement;
|
||||||
private searchTimeout?: NodeJS.Timeout;
|
private searchTimeout?: NodeJS.Timeout;
|
||||||
private currentVideos: VideoResult[] = [];
|
private currentVideos: VideoResult[] = [];
|
||||||
|
private loadingStartTime: number = 0;
|
||||||
|
private minLoadingDuration: number = 400; // Минимальное время показа спиннера (ms)
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.tg = (window as WindowWithTelegram).Telegram?.WebApp;
|
this.tg = (window as WindowWithTelegram).Telegram?.WebApp;
|
||||||
@@ -91,10 +93,13 @@ class QuixoticApp {
|
|||||||
return;
|
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.searchTimeout = setTimeout(() => {
|
||||||
this.search();
|
this.search();
|
||||||
}, 500);
|
}, 300);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Still handle Enter key for immediate search
|
// Still handle Enter key for immediate search
|
||||||
@@ -148,14 +153,35 @@ class QuixoticApp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const data: SearchResponse = await response.json();
|
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);
|
this.displayResults(data.videos);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Search error:', 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();
|
this.showNoResults();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private showLoading(): void {
|
private showLoading(): void {
|
||||||
|
// Record loading start time
|
||||||
|
this.loadingStartTime = Date.now();
|
||||||
|
|
||||||
// Clear any existing status messages
|
// Clear any existing status messages
|
||||||
const existingMessage = document.querySelector('.tg-status-message');
|
const existingMessage = document.querySelector('.tg-status-message');
|
||||||
if (existingMessage) {
|
if (existingMessage) {
|
||||||
@@ -201,8 +227,15 @@ class QuixoticApp {
|
|||||||
this.noResults.classList.add('tg-hidden');
|
this.noResults.classList.add('tg-hidden');
|
||||||
this.noResults.style.display = 'none';
|
this.noResults.style.display = 'none';
|
||||||
|
|
||||||
this.results.innerHTML = videos.map(video => `
|
// Use DocumentFragment for better performance
|
||||||
<div class='tg-list-item' onclick='app.convertVideo("${video.id}", "${this.escapeHtml(video.title)}", "${this.escapeHtml(video.url)}")'>
|
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__content'>
|
||||||
<div class='tg-list-item__media'>
|
<div class='tg-list-item__media'>
|
||||||
<img class='tg-list-item__thumbnail'
|
<img class='tg-list-item__thumbnail'
|
||||||
@@ -217,29 +250,35 @@ class QuixoticApp {
|
|||||||
<div class='tg-list-item__subtitle'>${this.escapeHtml(video.channel)}</div>
|
<div class='tg-list-item__subtitle'>${this.escapeHtml(video.channel)}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
`;
|
||||||
`).join('');
|
|
||||||
|
|
||||||
// Add touch event listeners to prevent sticky states
|
// Use event listener instead of inline onclick
|
||||||
this.results.querySelectorAll('.tg-list-item').forEach(item => {
|
item.addEventListener('click', () => {
|
||||||
const element = item as HTMLElement;
|
this.convertVideo(video.id, video.title, video.url);
|
||||||
|
});
|
||||||
|
|
||||||
// Reset visual state on touch end
|
// Reset visual state on touch end
|
||||||
element.addEventListener('touchend', () => {
|
item.addEventListener('touchend', () => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
element.blur();
|
item.blur();
|
||||||
element.style.background = '';
|
item.style.background = '';
|
||||||
element.style.transform = '';
|
item.style.transform = '';
|
||||||
}, 100);
|
}, 100);
|
||||||
});
|
}, { passive: true });
|
||||||
|
|
||||||
// Also handle mouse leave for desktop
|
// Also handle mouse leave for desktop
|
||||||
element.addEventListener('mouseleave', () => {
|
item.addEventListener('mouseleave', () => {
|
||||||
element.style.background = '';
|
item.style.background = '';
|
||||||
element.style.transform = '';
|
item.style.transform = '';
|
||||||
});
|
});
|
||||||
|
|
||||||
|
fragment.appendChild(item);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Clear and append once
|
||||||
|
this.results.innerHTML = '';
|
||||||
|
this.results.appendChild(fragment);
|
||||||
|
|
||||||
// Lazy load images with IntersectionObserver
|
// Lazy load images with IntersectionObserver
|
||||||
const imageObserver = new IntersectionObserver((entries) => {
|
const imageObserver = new IntersectionObserver((entries) => {
|
||||||
entries.forEach(entry => {
|
entries.forEach(entry => {
|
||||||
@@ -281,8 +320,8 @@ class QuixoticApp {
|
|||||||
const performer = video?.channel || 'Unknown Artist';
|
const performer = video?.channel || 'Unknown Artist';
|
||||||
const thumbnail = video?.thumbnail || '';
|
const thumbnail = video?.thumbnail || '';
|
||||||
|
|
||||||
// Find the clicked element by looking for the one that contains this videoId
|
// Find the clicked element using data attribute
|
||||||
const videoElement = document.querySelector(`[onclick*="${videoId}"]`) as HTMLElement;
|
const videoElement = document.querySelector(`[data-video-id="${videoId}"]`) as HTMLElement;
|
||||||
if (videoElement) {
|
if (videoElement) {
|
||||||
// Force blur to reset any active/focus states
|
// Force blur to reset any active/focus states
|
||||||
videoElement.blur();
|
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);
|
padding: var(--tg-spacing-xxl);
|
||||||
display: none;
|
display: none;
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.15s ease-in;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tg-spinner.tg-spinner--visible {
|
.tg-spinner.tg-spinner--visible {
|
||||||
display: block;
|
display: block;
|
||||||
|
animation: tg-fade-in 0.15s ease-in forwards;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tg-spinner__icon {
|
.tg-spinner__icon {
|
||||||
width: 32px;
|
width: 40px;
|
||||||
height: 32px;
|
height: 40px;
|
||||||
border: 2px solid var(--tg-color-secondary-bg);
|
border: 3px solid var(--tg-color-secondary-bg);
|
||||||
border-top: 2px solid var(--tg-color-button);
|
border-top: 3px solid var(--tg-color-button);
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
margin: 0 auto var(--tg-spacing-lg);
|
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 {
|
.tg-spinner__text {
|
||||||
font-size: var(--tg-font-size-sm);
|
font-size: var(--tg-font-size-sm);
|
||||||
color: var(--tg-color-hint);
|
color: var(--tg-color-hint);
|
||||||
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes tg-spin {
|
@keyframes tg-spin {
|
||||||
@@ -229,6 +233,17 @@ body {
|
|||||||
100% { transform: rotate(360deg); }
|
100% { transform: rotate(360deg); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@keyframes tg-fade-in {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translate(-50%, -45%);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* List component */
|
/* List component */
|
||||||
.tg-list {
|
.tg-list {
|
||||||
display: none;
|
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();
|
||||||
87
src/bot.ts
87
src/bot.ts
@@ -40,7 +40,27 @@ export class QuixoticBot {
|
|||||||
private db: Database;
|
private db: Database;
|
||||||
|
|
||||||
constructor(token: string, webAppUrl: string) {
|
constructor(token: string, webAppUrl: string) {
|
||||||
|
// 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.bot = new TelegramBot(token, { polling: true });
|
||||||
|
}
|
||||||
|
|
||||||
this.webAppUrl = webAppUrl;
|
this.webAppUrl = webAppUrl;
|
||||||
this.db = new Database();
|
this.db = new Database();
|
||||||
this.init();
|
this.init();
|
||||||
@@ -257,6 +277,7 @@ export class QuixoticBot {
|
|||||||
private async sendAudioFileInternal(chatId: number, audioUrlOrPath: string, title: string, performer?: string, thumbnail?: string): Promise<void> {
|
private async sendAudioFileInternal(chatId: number, audioUrlOrPath: string, title: string, performer?: string, thumbnail?: string): Promise<void> {
|
||||||
try {
|
try {
|
||||||
console.log(`📤 Sending: ${title} to chat ${chatId}`);
|
console.log(`📤 Sending: ${title} to chat ${chatId}`);
|
||||||
|
console.log(`📂 File source: ${audioUrlOrPath}`);
|
||||||
|
|
||||||
// Check if it's a URL or local file path
|
// Check if it's a URL or local file path
|
||||||
const isUrl = audioUrlOrPath.startsWith('http');
|
const isUrl = audioUrlOrPath.startsWith('http');
|
||||||
@@ -267,68 +288,86 @@ export class QuixoticBot {
|
|||||||
const urlParts = audioUrlOrPath.split('/');
|
const urlParts = audioUrlOrPath.split('/');
|
||||||
const filename = urlParts[urlParts.length - 1];
|
const filename = urlParts[urlParts.length - 1];
|
||||||
filePath = require('path').join(process.cwd(), 'downloads', filename);
|
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}`;
|
|
||||||
|
|
||||||
// Try sending as audio with custom filename
|
|
||||||
try {
|
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
|
|
||||||
// Check if file exists
|
// Check if file exists
|
||||||
if (!fs.existsSync(filePath)) {
|
if (!fs.existsSync(filePath)) {
|
||||||
|
console.error(`❌ File not found: ${filePath}`);
|
||||||
throw new Error('File not found: ' + filePath);
|
throw new Error('File not found: ' + filePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.bot.sendAudio(chatId, 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 {
|
||||||
|
await this.bot.sendAudio(chatId, fileStream, {
|
||||||
title: title,
|
title: title,
|
||||||
performer: performer,
|
performer: performer || 'Unknown Artist',
|
||||||
caption: undefined,
|
caption: undefined,
|
||||||
thumbnail: thumbnail,
|
thumbnail: undefined, // Thumbnail requires special handling
|
||||||
parse_mode: undefined
|
parse_mode: undefined
|
||||||
}, {
|
}, {
|
||||||
filename: customFilename,
|
filename: customFilename,
|
||||||
contentType: 'audio/mpeg'
|
contentType: 'audio/mpeg'
|
||||||
});
|
});
|
||||||
console.log(`✅ Audio sent: ${title}`);
|
console.log(`✅ Audio sent successfully: ${title}`);
|
||||||
return;
|
return;
|
||||||
|
|
||||||
} catch (error: any) {
|
} 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 {
|
try {
|
||||||
await this.bot.sendDocument(chatId, filePath, {
|
console.log('🔄 Retrying as document...');
|
||||||
caption: undefined,
|
const docStream = fs.createReadStream(filePath);
|
||||||
|
|
||||||
|
await this.bot.sendDocument(chatId, docStream, {
|
||||||
|
caption: `🎵 ${title}\n👤 ${performer || 'Unknown Artist'}`,
|
||||||
parse_mode: undefined
|
parse_mode: undefined
|
||||||
}, {
|
}, {
|
||||||
filename: customFilename,
|
filename: customFilename,
|
||||||
contentType: 'audio/mpeg'
|
contentType: 'audio/mpeg'
|
||||||
});
|
});
|
||||||
console.log(`✅ Document sent: ${title}`);
|
console.log(`✅ Document sent successfully: ${title}`);
|
||||||
return;
|
return;
|
||||||
|
|
||||||
} catch (documentError: any) {
|
} catch (documentError: any) {
|
||||||
|
console.error('❌ Document send also failed:', documentError.message);
|
||||||
throw documentError;
|
throw documentError;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (error: any) {
|
} 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 {
|
try {
|
||||||
const message = audioUrlOrPath.startsWith('http')
|
await this.bot.sendMessage(chatId,
|
||||||
? `❌ Не удалось отправить файл.\n🎵 ${title}\n🔗 ${audioUrlOrPath}`
|
`❌ Не удалось отправить файл.\n🎵 ${title}\n\nПопробуйте другой трек.`
|
||||||
: `❌ Не удалось отправить файл: ${title}`;
|
);
|
||||||
|
|
||||||
await this.bot.sendMessage(chatId, message);
|
|
||||||
} catch {
|
} 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
|
// Error handler
|
||||||
app.use((_err: Error, _req: Request, res: Response) => {
|
app.use((err: Error, _req: Request, res: Response, _next: any) => {
|
||||||
console.error(_err.stack);
|
console.error(err.stack);
|
||||||
res.status(500).json({ error: 'Something went wrong!' });
|
res.status(500).json({ error: 'Something went wrong!' });
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -316,7 +316,7 @@ app.listen(port, () => {
|
|||||||
const botToken = process.env.TELEGRAM_BOT_TOKEN;
|
const botToken = process.env.TELEGRAM_BOT_TOKEN;
|
||||||
const webAppUrl = process.env.WEB_APP_URL || `http://localhost:${port}`;
|
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 {
|
try {
|
||||||
const botInstance = new QuixoticBot(botToken, webAppUrl);
|
const botInstance = new QuixoticBot(botToken, webAppUrl);
|
||||||
// Store bot instance globally for API access
|
// Store bot instance globally for API access
|
||||||
@@ -325,9 +325,30 @@ if (botToken && botToken.length > 10) {
|
|||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('❌ Bot initialization failed:', error.message);
|
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 {
|
} 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;
|
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"
|
resolved "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz"
|
||||||
integrity sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==
|
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"
|
version "3.1.2"
|
||||||
resolved "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz"
|
resolved "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz"
|
||||||
integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==
|
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"
|
version "1.5.5"
|
||||||
resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz"
|
resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz"
|
||||||
integrity sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==
|
integrity sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==
|
||||||
@@ -160,6 +176,14 @@
|
|||||||
"@jridgewell/resolve-uri" "^3.0.3"
|
"@jridgewell/resolve-uri" "^3.0.3"
|
||||||
"@jridgewell/sourcemap-codec" "^1.4.10"
|
"@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":
|
"@nodelib/fs.scandir@2.1.5":
|
||||||
version "2.1.5"
|
version "2.1.5"
|
||||||
resolved "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz"
|
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:
|
dependencies:
|
||||||
fill-range "^7.1.1"
|
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:
|
bytes@3.1.2:
|
||||||
version "3.1.2"
|
version "3.1.2"
|
||||||
resolved "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz"
|
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"
|
resolved "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz"
|
||||||
integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==
|
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:
|
caseless@~0.12.0:
|
||||||
version "0.12.0"
|
version "0.12.0"
|
||||||
resolved "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz"
|
resolved "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz"
|
||||||
@@ -750,6 +787,13 @@ chokidar@^3.5.2:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
fsevents "~2.3.2"
|
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:
|
color-convert@^2.0.1:
|
||||||
version "2.0.1"
|
version "2.0.1"
|
||||||
resolved "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz"
|
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:
|
dependencies:
|
||||||
delayed-stream "~1.0.0"
|
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:
|
compressible@~2.0.18:
|
||||||
version "2.0.18"
|
version "2.0.18"
|
||||||
resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.18.tgz#af53cca6b070d4c3c0750fbd77286a6d7cc46fba"
|
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"
|
resolved "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz"
|
||||||
integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==
|
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:
|
dotenv@^8.2.0:
|
||||||
version "8.6.0"
|
version "8.6.0"
|
||||||
resolved "https://registry.npmjs.org/dotenv/-/dotenv-8.6.0.tgz"
|
resolved "https://registry.npmjs.org/dotenv/-/dotenv-8.6.0.tgz"
|
||||||
@@ -982,6 +1044,11 @@ end-of-stream@^1.1.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
once "^1.4.0"
|
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:
|
es-abstract@^1.23.5, es-abstract@^1.23.6, es-abstract@^1.23.9:
|
||||||
version "1.24.0"
|
version "1.24.0"
|
||||||
resolved "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.0.tgz"
|
resolved "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.0.tgz"
|
||||||
@@ -1540,6 +1607,19 @@ hasown@^2.0.2:
|
|||||||
dependencies:
|
dependencies:
|
||||||
function-bind "^1.1.2"
|
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:
|
http-errors@2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz"
|
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"
|
resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz"
|
||||||
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
|
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:
|
m3u8stream@^0.8.0:
|
||||||
version "0.8.6"
|
version "0.8.6"
|
||||||
resolved "https://registry.npmjs.org/m3u8stream/-/m3u8stream-0.8.6.tgz"
|
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"
|
resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.4.tgz#777948e2452651c570b712dd01c23e262713fff7"
|
||||||
integrity sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==
|
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:
|
node-telegram-bot-api@^0.64.0:
|
||||||
version "0.64.0"
|
version "0.64.0"
|
||||||
resolved "https://registry.npmjs.org/node-telegram-bot-api/-/node-telegram-bot-api-0.64.0.tgz"
|
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:
|
dependencies:
|
||||||
p-limit "^3.0.2"
|
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:
|
parent-module@^1.0.0:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz"
|
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"
|
resolved "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz"
|
||||||
integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==
|
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:
|
path-exists@^4.0.0:
|
||||||
version "4.0.0"
|
version "4.0.0"
|
||||||
resolved "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz"
|
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"
|
gopd "^1.2.0"
|
||||||
set-function-name "^2.0.2"
|
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:
|
request-promise-core@1.1.3:
|
||||||
version "1.1.3"
|
version "1.1.3"
|
||||||
resolved "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.3.tgz"
|
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"
|
resolved "https://registry.npmjs.org/soundcloud-key-fetch/-/soundcloud-key-fetch-1.0.13.tgz"
|
||||||
integrity sha512-X1ketDIELpXOj2zuNNlTEtEy8y9Zu5R9m2lzkocXkG0GmUMht2ZypS8mtEDAWtEeqnEqkkLxipOsly+qU1gb6Q==
|
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:
|
split2@^4.1.0:
|
||||||
version "4.2.0"
|
version "4.2.0"
|
||||||
resolved "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz"
|
resolved "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz"
|
||||||
@@ -2695,6 +2824,16 @@ supports-color@^7.1.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
has-flag "^4.0.0"
|
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:
|
tldts-core@^6.1.86:
|
||||||
version "6.1.86"
|
version "6.1.86"
|
||||||
resolved "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.86.tgz"
|
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"
|
v8-compile-cache-lib "^3.0.1"
|
||||||
yn "3.1.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:
|
tunnel-agent@^0.6.0:
|
||||||
version "0.6.0"
|
version "0.6.0"
|
||||||
resolved "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz"
|
resolved "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz"
|
||||||
|
|||||||
Reference in New Issue
Block a user