import { NextRequest, NextResponse } from 'next/server'; import { TelegramUpdate, sendMessage, sendMediaToChannel, sendLog, downloadTelegramFile, getTelegramFileUrl } from '@/lib/telegram'; import { uploadToHuggingFace, isHuggingFaceConfigured } from '@/lib/huggingface'; import { saveImage, generateId, getStats, registerUser } from '@/lib/db'; export async function POST(req: NextRequest) { try { const body: TelegramUpdate = await req.json(); if (!body.message) { return new NextResponse('OK'); } const chatId = body.message.chat.id; const text = body.message.text; const photo = body.message.photo; const animation = body.message.animation; const document = body.message.document; const replyTo = body.message.reply_to_message; const from = body.message.from; const userLink = from.username ? `@${from.username}` : `${from.first_name} [${from.id}]`; if (text) { const command = text.split(' ')[0].split('@')[0].toLowerCase(); if (command === '/start' || command === '/help') { if (command === '/start') { await registerUser(from.id); await sendLog(`šŸ‘¤ New User Started Bot\n\nUser: ${userLink}\nID: ${from.id}`); } await sendMessage(chatId, `✨ VoltEdge Bot Help\n\n` + `I can host your media at lightning speed using our edge infrastructure.\n\n` + `šŸš€ How to Upload:\n` + `• Send a Photo/Video/GIF directly to me.\n` + `• Send an Image/Video/GIF as a Document.\n` + `• Or Reply to an existing Media with /upload or /tgm.\n\n` + `Commands:\n` + `/stats - Show bot statistics\n` + `/upload or /tgm - Upload a replied Media\n` + `/help - Show this message`, 'HTML', { inline_keyboard: [[ { text: "🌐 Visit Website", url: "https://hunters.indevs.in/" } ]] } ); return new NextResponse('OK'); } if (command === '/stats') { const stats = await getStats(); await sendMessage(chatId, `šŸ“Š VoltEdge Statistics\n\n` + `šŸ‘„ Total Users: ${stats.totalUsers}\n` + `šŸ–¼ļø Images: ${stats.totalImages}\n` + `šŸŽ¬ Videos/GIFs: ${stats.totalVideos}\n` + `šŸ¤– Bot Uploads: ${stats.botUploads}\n` + `🌐 Web Uploads: ${stats.webUploads}\n` + `šŸ“¶ Ping: ${stats.ping}ms`, 'HTML' ); return new NextResponse('OK'); } if (command === '/upload' || command === '/tgm') { // Check if it's a reply to an image if (replyTo) { if (replyTo.photo && replyTo.photo.length > 0) { const largestPhoto = replyTo.photo[replyTo.photo.length - 1]; await processFile(chatId, largestPhoto.file_id, largestPhoto.file_size, 'image/jpeg', userLink, from.id, 'photo'); return new NextResponse('OK'); } if (replyTo.animation) { await processFile(chatId, replyTo.animation.file_id, replyTo.animation.file_size, replyTo.animation.mime_type || 'image/gif', userLink, from.id, 'animation'); return new NextResponse('OK'); } if (replyTo.document && (replyTo.document.mime_type?.startsWith('image/') || replyTo.document.mime_type?.startsWith('video/'))) { const type = replyTo.document.mime_type?.startsWith('video/') ? 'animation' : 'photo'; await processFile(chatId, replyTo.document.file_id, replyTo.document.file_size, replyTo.document.mime_type, userLink, from.id, type); return new NextResponse('OK'); } } // If not a reply, show instructions await sendMessage(chatId, `VoltEdge Upload Mode:\n\n` + `1. Directly send a photo to this bot.\n` + `2. Or send an image as a "File/Document".\n` + `3. Or reply to an image with /upload.\n\n` + `I will instantly return a high-speed VoltEdge link!` ); return new NextResponse('OK'); } // Fallback for unknown text if (body.message.chat.type === 'private') { await sendMessage(chatId, `ā“ I'm not sure what you mean.\n\n` + `Just send me any Photo or Video/GIF and I will host it for you instantly! Or type /help for commands.`, 'HTML' ); } return new NextResponse('OK'); } // Handle Photo if (photo && photo.length > 0) { if (body.message.chat.type === 'private') { const largestPhoto = photo[photo.length - 1]; await processFile(chatId, largestPhoto.file_id, largestPhoto.file_size, 'image/jpeg', userLink, from.id, 'photo'); } return new NextResponse('OK'); } // Handle Animation (GIF) if (animation) { if (body.message.chat.type === 'private') { await processFile(chatId, animation.file_id, animation.file_size, animation.mime_type || 'image/gif', userLink, from.id, 'animation'); } return new NextResponse('OK'); } // Handle Document (image or video/gif) if (document) { // Only process direct documents in PRIVATE chats if (body.message.chat.type === 'private') { const mimeType = document.mime_type || ''; if (mimeType.startsWith('image/')) { await processFile(chatId, document.file_id, document.file_size, mimeType, userLink, from.id, 'photo'); } else if (mimeType.startsWith('video/')) { await processFile(chatId, document.file_id, document.file_size, mimeType, userLink, from.id, 'animation'); } else { await sendMessage(chatId, "āŒ Please send only image or GIF files."); } } return new NextResponse('OK'); } return new NextResponse('OK'); } catch (error) { console.error('Webhook error:', error); await sendLog(`āš ļø Webhook Error\n\nError: ${error}`); return new NextResponse('OK'); // Always return OK to Telegram } } async function processFile(chatId: number, fileId: string, fileSize: number, mimeType: string, userLink: string, userId: number | string, mediaType: 'photo' | 'animation' | 'video') { try { // Enforce 2GB limit const MAX_SIZE = 2 * 1024 * 1024 * 1024; // 2GB if (fileSize > MAX_SIZE) { await sendMessage(chatId, "āŒ File too large. Max size is 2GB."); return; } const id = generateId(); const baseUrl = process.env.NEXT_PUBLIC_BASE_URL || 'https://hunters.indevs.in/'; // Determine storage based on file size const LARGE_FILE_THRESHOLD = 50 * 1024 * 1024; // 50MB const isLargeFile = fileSize > LARGE_FILE_THRESHOLD; const useHFForLargeFiles = isHuggingFaceConfigured() && process.env.USE_HF_FOR_LARGE_FILES === 'true'; let storageResult: { file_id: string; file_url?: string }; let storageType: 'telegram' | 'huggingface' = 'telegram'; if (useHFForLargeFiles && isLargeFile) { // For large files, download from Telegram and upload to HF Hub try { const fileBlob = await downloadTelegramFile(fileId); const fileName = `bot-upload-${id}`; const hfResult = await uploadToHuggingFace(fileBlob, fileName, id); storageResult = { file_id: hfResult.file_id, file_url: hfResult.file_url }; storageType = 'huggingface'; // Still forward to Telegram chat for backup/notification await sendMediaToChannel(fileId, `šŸ‘¤ Uploaded by: ${userLink}\nšŸ“¦ Stored in HF Hub\nšŸ”— HF URL: ${hfResult.file_url}`, mediaType); } catch (hfError: any) { console.error('HF upload failed for bot, using Telegram:', hfError); // Fallback to Telegram await sendMediaToChannel(fileId, `šŸ‘¤ Uploaded by: ${userLink}`, mediaType); storageResult = { file_id: fileId }; } } else { // Use Telegram for small files await sendMediaToChannel(fileId, `šŸ‘¤ Uploaded by: ${userLink}`, mediaType); storageResult = { file_id: fileId }; } // Save to DB const record: any = { id, telegram_file_id: storageResult.file_id, storage_type: storageType, storage_url: storageResult.file_url, created_at: Date.now(), metadata: { size: fileSize, type: mimeType } }; await saveImage(record, 'bot', userId); const publicUrl = `${baseUrl}/i/${id}`; await sendMessage(chatId, `āœ… File Uploaded Successfully!\n\n` + `šŸ”— Link: ${publicUrl}\n` + (storageType === 'huggingface' ? `šŸ“¦ Stored in: Hugging Face Hub\n` : '') + `⚔ Hosted on VoltEdge`, 'HTML' ); await sendLog(`šŸ“¤ New Bot Upload\n\nUser: ${userLink}\nType: ${mimeType}\nSize: ${(fileSize / 1024 / 1024).toFixed(2)} MB\nStorage: ${storageType === 'huggingface' ? 'HF Hub' : 'Telegram'}\nLink: ${publicUrl}`); } catch (error) { console.error('Processing error:', error); await sendLog(`āŒ Upload Processing Error\n\nUser: ${userLink}\nError: ${error}`); await sendMessage(chatId, "āŒ Failed to process your image. Please try again later."); } }