fix authors?

This commit is contained in:
Andrey Kondratev
2025-11-10 16:51:57 +05:00
parent beb2d19019
commit 21a32ffc79
5 changed files with 83 additions and 28 deletions

View File

@@ -912,7 +912,6 @@ class QuixoticApp {
const data: ConvertResponse = await response.json(); const data: ConvertResponse = await response.json();
if (data.audioUrl) { if (data.audioUrl) {
if (this.tg) { if (this.tg) {
const userId = this.tg?.initDataUnsafe?.user?.id; const userId = this.tg?.initDataUnsafe?.user?.id;
if (!userId) { if (!userId) {

View File

@@ -651,19 +651,21 @@ body {
/* Status message */ /* Status message */
.tg-status-message { .tg-status-message {
position: fixed; position: fixed;
top: 20px; top: 12px;
left: var(--tg-spacing-lg); left: var(--tg-spacing-md);
right: var(--tg-spacing-lg); right: var(--tg-spacing-md);
z-index: 1000; z-index: 1000;
padding: var(--tg-spacing-md) var(--tg-spacing-lg); padding: 10px 14px;
border-radius: var(--tg-border-radius); border-radius: 10px;
font-size: var(--tg-font-size-sm); font-size: 13px;
font-weight: var(--tg-font-weight-medium); font-weight: var(--tg-font-weight-medium);
animation: tg-slide-down 0.3s ease-out; animation: tg-slide-down 0.3s ease-out;
display: flex; display: flex;
align-items: center; align-items: center;
gap: var(--tg-spacing-sm); gap: 8px;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.25), 0 4px 12px rgba(0, 0, 0, 0.15); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2), 0 2px 6px rgba(0, 0, 0, 0.12);
max-width: 320px;
margin: 0 auto;
} }
.tg-status-message--success { .tg-status-message--success {
@@ -687,7 +689,7 @@ body {
@keyframes tg-slide-down { @keyframes tg-slide-down {
from { from {
opacity: 0; opacity: 0;
transform: translateY(-20px); transform: translateY(-12px);
} }
to { to {

View File

@@ -272,6 +272,7 @@ export class QuixoticBot {
// Public method for external API calls // Public method for external API calls
public async sendAudioFile(chatId: number, audioUrl: string, title: string, performer?: string, thumbnail?: string): Promise<void> { public async sendAudioFile(chatId: number, audioUrl: string, title: string, performer?: string, thumbnail?: string): Promise<void> {
logger.debug(`sendAudioFile called with performer: ${performer}, thumbnail: ${thumbnail}`);
return this.sendAudioFileInternal(chatId, audioUrl, title, performer, thumbnail); return this.sendAudioFileInternal(chatId, audioUrl, title, performer, thumbnail);
} }
@@ -279,7 +280,8 @@ export class QuixoticBot {
try { try {
logger.telegram('Sending audio', `${title} to chat ${chatId}`); logger.telegram('Sending audio', `${title} to chat ${chatId}`);
logger.debug(`File source: ${audioUrlOrPath}`); logger.debug(`File source: ${audioUrlOrPath}`);
logger.debug(`Thumbnail: ${thumbnail}`); logger.debug(`Performer: ${performer || 'Not provided'}`);
logger.debug(`Thumbnail: ${thumbnail || 'Not provided'}`);
// 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');
@@ -318,25 +320,70 @@ export class QuixoticBot {
let thumbnailPath: string | undefined; let thumbnailPath: string | undefined;
if (thumbnail && thumbnail.startsWith('http')) { if (thumbnail && thumbnail.startsWith('http')) {
try { try {
logger.debug('Downloading thumbnail...'); logger.debug(`Downloading thumbnail from: ${thumbnail}`);
const thumbnailFilename = `thumb_${Date.now()}.jpg`; const thumbnailFilename = `thumb_${Date.now()}.jpg`;
thumbnailPath = path.join(process.cwd(), 'downloads', thumbnailFilename); thumbnailPath = path.join(process.cwd(), 'downloads', thumbnailFilename);
await new Promise<void>((resolve, reject) => { await new Promise<void>((resolve, reject) => {
const file = fs.createWriteStream(thumbnailPath); const file = fs.createWriteStream(thumbnailPath!);
https.get(thumbnail, (response: any) => {
response.pipe(file); // Handle both http and https
file.on('finish', () => { const protocol = thumbnail.startsWith('https') ? https : require('http');
const request = protocol.get(thumbnail, (response: any) => {
// Follow redirects
if (response.statusCode === 301 || response.statusCode === 302) {
const redirectUrl = response.headers.location;
logger.debug(`Following redirect to: ${redirectUrl}`);
file.close(); file.close();
resolve(); fs.unlink(thumbnailPath!, () => {});
});
}).on('error', (err: any) => { const redirectProtocol = redirectUrl.startsWith('https') ? https : require('http');
fs.unlink(thumbnailPath, () => {}); redirectProtocol.get(redirectUrl, (redirectResponse: any) => {
redirectResponse.pipe(file);
file.on('finish', () => {
file.close();
logger.success(`Thumbnail downloaded: ${thumbnailPath} (${fs.statSync(thumbnailPath!).size} bytes)`);
resolve();
});
}).on('error', (err: any) => {
file.close();
fs.unlink(thumbnailPath!, () => {});
reject(err);
});
} else {
response.pipe(file);
file.on('finish', () => {
file.close();
const fileSize = fs.statSync(thumbnailPath!).size;
logger.success(`Thumbnail downloaded: ${thumbnailPath} (${fileSize} bytes)`);
// Check if file is valid (at least 1KB)
if (fileSize < 1000) {
logger.warn('Thumbnail file too small, may be invalid');
fs.unlink(thumbnailPath!, () => {});
thumbnailPath = undefined;
}
resolve();
});
}
});
request.on('error', (err: any) => {
file.close();
fs.unlink(thumbnailPath!, () => {});
reject(err); reject(err);
}); });
// Set timeout for thumbnail download
request.setTimeout(10000, () => {
request.destroy();
file.close();
fs.unlink(thumbnailPath!, () => {});
reject(new Error('Thumbnail download timeout'));
});
}); });
logger.success(`Thumbnail downloaded: ${thumbnailPath}`);
} catch (thumbError: any) { } catch (thumbError: any) {
logger.warn('Failed to download thumbnail:', thumbError.message); logger.warn('Failed to download thumbnail:', thumbError.message);
thumbnailPath = undefined; thumbnailPath = undefined;

View File

@@ -158,8 +158,8 @@ app.post('/api/search', async (req: Request, res: Response) => {
// Convert video to MP3 // Convert video to MP3
app.post('/api/convert', async (req: Request, res: Response) => { app.post('/api/convert', async (req: Request, res: Response) => {
try { try {
const { videoId, title, userId, url }: { videoId?: string; title?: string; userId?: string; url?: string } = req.body; const { videoId, title, userId, url, performer }: { videoId?: string; title?: string; userId?: string; url?: string; performer?: string } = req.body;
logger.info(`Convert request received: ${title} (ID: ${videoId})`); logger.info(`Convert request received: ${title} by ${performer || 'Unknown'} (ID: ${videoId})`);
if (!videoId) { if (!videoId) {
return res.status(400).json({ error: 'Video ID is required' }); return res.status(400).json({ error: 'Video ID is required' });

View File

@@ -49,22 +49,29 @@ export class SoundCloudService {
// -large (100x100) -> -t500x500 (500x500) or -t300x300 (300x300) // -large (100x100) -> -t500x500 (500x500) or -t300x300 (300x300)
// Try to get the highest quality version available // Try to get the highest quality version available
let highQualityUrl = originalUrl;
if (originalUrl.includes('-large.')) { if (originalUrl.includes('-large.')) {
// Replace -large with -t500x500 for better quality // Replace -large with -t500x500 for better quality
return originalUrl.replace('-large.', '-t500x500.'); highQualityUrl = originalUrl.replace('-large.', '-t500x500.');
} else if (originalUrl.includes('-crop.')) { } else if (originalUrl.includes('-crop.')) {
// If it's crop (400x400), try to get t500x500 or keep crop // If it's crop (400x400), try to get t500x500 or keep crop
return originalUrl.replace('-crop.', '-t500x500.'); highQualityUrl = originalUrl.replace('-crop.', '-t500x500.');
} else if (originalUrl.includes('-t300x300.')) { } else if (originalUrl.includes('-t300x300.')) {
// If it's already 300x300, try to upgrade to 500x500 // If it's already 300x300, try to upgrade to 500x500
return originalUrl.replace('-t300x300.', '-t500x500.'); highQualityUrl = originalUrl.replace('-t300x300.', '-t500x500.');
} else if (originalUrl.includes('default_avatar_large.png')) { } else if (originalUrl.includes('default_avatar_large.png')) {
// For default avatars, use a higher quality placeholder // For default avatars, use a higher quality placeholder
return 'https://via.placeholder.com/500x500/007AFF/ffffff?text=🎵'; highQualityUrl = 'https://via.placeholder.com/500x500/007AFF/ffffff?text=🎵';
}
// Log transformation if changed
if (highQualityUrl !== originalUrl) {
logger.debug(`Thumbnail upgraded: ${originalUrl.substring(0, 60)}... -> ${highQualityUrl.substring(0, 60)}...`);
} }
// If no size suffix found or already high quality, return original // If no size suffix found or already high quality, return original
return originalUrl; return highQualityUrl;
} }
async searchTracks(query: string, maxResults: number = 10, offset: number = 0): Promise<TrackResult[]> { async searchTracks(query: string, maxResults: number = 10, offset: number = 0): Promise<TrackResult[]> {