fix authors?
This commit is contained in:
@@ -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) {
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
61
src/bot.ts
61
src/bot.ts
@@ -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
|
||||||
|
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.on('finish', () => {
|
||||||
file.close();
|
file.close();
|
||||||
|
logger.success(`Thumbnail downloaded: ${thumbnailPath} (${fs.statSync(thumbnailPath!).size} bytes)`);
|
||||||
resolve();
|
resolve();
|
||||||
});
|
});
|
||||||
}).on('error', (err: any) => {
|
}).on('error', (err: any) => {
|
||||||
fs.unlink(thumbnailPath, () => {});
|
file.close();
|
||||||
|
fs.unlink(thumbnailPath!, () => {});
|
||||||
reject(err);
|
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) {
|
} catch (thumbError: any) {
|
||||||
logger.warn('Failed to download thumbnail:', thumbError.message);
|
logger.warn('Failed to download thumbnail:', thumbError.message);
|
||||||
thumbnailPath = undefined;
|
thumbnailPath = undefined;
|
||||||
|
|||||||
@@ -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' });
|
||||||
|
|||||||
@@ -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[]> {
|
||||||
|
|||||||
Reference in New Issue
Block a user