fix authors?
This commit is contained in:
@@ -912,7 +912,6 @@ class QuixoticApp {
|
||||
const data: ConvertResponse = await response.json();
|
||||
|
||||
if (data.audioUrl) {
|
||||
|
||||
if (this.tg) {
|
||||
const userId = this.tg?.initDataUnsafe?.user?.id;
|
||||
if (!userId) {
|
||||
|
||||
@@ -651,19 +651,21 @@ body {
|
||||
/* Status message */
|
||||
.tg-status-message {
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
left: var(--tg-spacing-lg);
|
||||
right: var(--tg-spacing-lg);
|
||||
top: 12px;
|
||||
left: var(--tg-spacing-md);
|
||||
right: var(--tg-spacing-md);
|
||||
z-index: 1000;
|
||||
padding: var(--tg-spacing-md) var(--tg-spacing-lg);
|
||||
border-radius: var(--tg-border-radius);
|
||||
font-size: var(--tg-font-size-sm);
|
||||
padding: 10px 14px;
|
||||
border-radius: 10px;
|
||||
font-size: 13px;
|
||||
font-weight: var(--tg-font-weight-medium);
|
||||
animation: tg-slide-down 0.3s ease-out;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--tg-spacing-sm);
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.25), 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
gap: 8px;
|
||||
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 {
|
||||
@@ -687,7 +689,7 @@ body {
|
||||
@keyframes tg-slide-down {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(-20px);
|
||||
transform: translateY(-12px);
|
||||
}
|
||||
|
||||
to {
|
||||
|
||||
61
src/bot.ts
61
src/bot.ts
@@ -272,6 +272,7 @@ export class QuixoticBot {
|
||||
|
||||
// Public method for external API calls
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -279,7 +280,8 @@ export class QuixoticBot {
|
||||
try {
|
||||
logger.telegram('Sending audio', `${title} to chat ${chatId}`);
|
||||
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
|
||||
const isUrl = audioUrlOrPath.startsWith('http');
|
||||
@@ -318,25 +320,70 @@ export class QuixoticBot {
|
||||
let thumbnailPath: string | undefined;
|
||||
if (thumbnail && thumbnail.startsWith('http')) {
|
||||
try {
|
||||
logger.debug('Downloading thumbnail...');
|
||||
logger.debug(`Downloading thumbnail from: ${thumbnail}`);
|
||||
const thumbnailFilename = `thumb_${Date.now()}.jpg`;
|
||||
thumbnailPath = path.join(process.cwd(), 'downloads', thumbnailFilename);
|
||||
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
const file = fs.createWriteStream(thumbnailPath);
|
||||
https.get(thumbnail, (response: any) => {
|
||||
response.pipe(file);
|
||||
const file = fs.createWriteStream(thumbnailPath!);
|
||||
|
||||
// Handle both http and https
|
||||
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();
|
||||
fs.unlink(thumbnailPath!, () => {});
|
||||
|
||||
const redirectProtocol = redirectUrl.startsWith('https') ? https : require('http');
|
||||
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) => {
|
||||
fs.unlink(thumbnailPath, () => {});
|
||||
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);
|
||||
});
|
||||
|
||||
// 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) {
|
||||
logger.warn('Failed to download thumbnail:', thumbError.message);
|
||||
thumbnailPath = undefined;
|
||||
|
||||
@@ -158,8 +158,8 @@ app.post('/api/search', async (req: Request, res: Response) => {
|
||||
// Convert video to MP3
|
||||
app.post('/api/convert', async (req: Request, res: Response) => {
|
||||
try {
|
||||
const { videoId, title, userId, url }: { videoId?: string; title?: string; userId?: string; url?: string } = req.body;
|
||||
logger.info(`Convert request received: ${title} (ID: ${videoId})`);
|
||||
const { videoId, title, userId, url, performer }: { videoId?: string; title?: string; userId?: string; url?: string; performer?: string } = req.body;
|
||||
logger.info(`Convert request received: ${title} by ${performer || 'Unknown'} (ID: ${videoId})`);
|
||||
|
||||
if (!videoId) {
|
||||
return res.status(400).json({ error: 'Video ID is required' });
|
||||
|
||||
@@ -49,22 +49,29 @@ export class SoundCloudService {
|
||||
// -large (100x100) -> -t500x500 (500x500) or -t300x300 (300x300)
|
||||
// Try to get the highest quality version available
|
||||
|
||||
let highQualityUrl = originalUrl;
|
||||
|
||||
if (originalUrl.includes('-large.')) {
|
||||
// Replace -large with -t500x500 for better quality
|
||||
return originalUrl.replace('-large.', '-t500x500.');
|
||||
highQualityUrl = originalUrl.replace('-large.', '-t500x500.');
|
||||
} else if (originalUrl.includes('-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.')) {
|
||||
// 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')) {
|
||||
// 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
|
||||
return originalUrl;
|
||||
return highQualityUrl;
|
||||
}
|
||||
|
||||
async searchTracks(query: string, maxResults: number = 10, offset: number = 0): Promise<TrackResult[]> {
|
||||
|
||||
Reference in New Issue
Block a user