Spaces:
Paused
Paused
| import { NextRequest, NextResponse } from 'next/server'; | |
| import { validateApiKey, createUnauthorizedResponse } from '@/lib/middleware/api-auth'; | |
| interface DownloadLink { | |
| url: string; | |
| server: string; | |
| type: 'download'; | |
| resumeSupported: boolean; | |
| description: string; | |
| } | |
| interface VCloudResponse { | |
| success: boolean; | |
| originalUrl?: string; | |
| downloadLinks?: DownloadLink[]; | |
| totalLinks?: number; | |
| extractedAt?: string; | |
| remainingRequests?: number; | |
| error?: string; | |
| message?: string; | |
| details?: string; | |
| usage?: string; | |
| providedDomain?: string; | |
| } | |
| // Add User-Agent rotation to avoid detection | |
| const USER_AGENTS = [ | |
| 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', | |
| 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36', | |
| 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', | |
| 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/121.0', | |
| 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36' | |
| ]; | |
| function getRandomUserAgent(): string { | |
| return USER_AGENTS[Math.floor(Math.random() * USER_AGENTS.length)]; | |
| } | |
| function getRequestHeaders(referer?: string): HeadersInit { | |
| return { | |
| 'User-Agent': getRandomUserAgent(), | |
| 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7', | |
| 'Accept-Language': 'en-US,en;q=0.9', | |
| 'Accept-Encoding': 'gzip, deflate, br', | |
| 'DNT': '1', | |
| 'Connection': 'keep-alive', | |
| 'Upgrade-Insecure-Requests': '1', | |
| 'Sec-Fetch-Dest': 'document', | |
| 'Sec-Fetch-Mode': 'navigate', | |
| 'Sec-Fetch-Site': 'none', | |
| 'Sec-Fetch-User': '?1', | |
| 'Cache-Control': 'max-age=0', | |
| ...(referer && { 'Referer': referer }) | |
| }; | |
| } | |
| async function extractVCloudUrl(vcloudUrl: string): Promise<string | null> { | |
| try { | |
| console.log(`Extracting VCloud URL from: ${vcloudUrl}`); | |
| // Add retry logic with different approaches | |
| for (let attempt = 1; attempt <= 3; attempt++) { | |
| try { | |
| const response = await fetch(vcloudUrl, { | |
| headers: getRequestHeaders(), | |
| redirect: 'follow', | |
| signal: AbortSignal.timeout(15000) // 15 second timeout | |
| }); | |
| if (!response.ok) { | |
| if (response.status === 403 && attempt < 3) { | |
| console.log(`Attempt ${attempt} failed with 403, retrying...`); | |
| await new Promise(resolve => setTimeout(resolve, 1000 * attempt)); // Progressive delay | |
| continue; | |
| } | |
| throw new Error(`Failed to fetch VCloud page: ${response.status}`); | |
| } | |
| const html = await response.text(); | |
| // Extract the URL from the JavaScript variable | |
| const urlMatch = html.match(/var\s+url\s*=\s*['"`]([^'"`]+)['"`]/); | |
| if (urlMatch && urlMatch[1]) { | |
| const extractedUrl = urlMatch[1]; | |
| console.log(`Successfully extracted URL: ${extractedUrl}`); | |
| return extractedUrl; | |
| } | |
| // Alternative pattern matching | |
| const altUrlMatch = html.match(/url\s*=\s*['"`]([^'"`]+)['"`]/); | |
| if (altUrlMatch && altUrlMatch[1]) { | |
| const extractedUrl = altUrlMatch[1]; | |
| console.log(`Successfully extracted URL (alternative pattern): ${extractedUrl}`); | |
| return extractedUrl; | |
| } | |
| // Look for hubcloud.php URLs | |
| const hubcloudMatch = html.match(/['"`](https?:\/\/[^'"`]*hubcloud\.php[^'"`]*)['"`]/); | |
| if (hubcloudMatch && hubcloudMatch[1]) { | |
| const extractedUrl = hubcloudMatch[1]; | |
| console.log(`Successfully extracted hubcloud URL: ${extractedUrl}`); | |
| return extractedUrl; | |
| } | |
| console.log('No URL found in the VCloud page'); | |
| return null; | |
| } catch (fetchError) { | |
| if (attempt === 3) { | |
| throw fetchError; | |
| } | |
| console.log(`Attempt ${attempt} failed:`, fetchError); | |
| await new Promise(resolve => setTimeout(resolve, 1000 * attempt)); | |
| } | |
| } | |
| return null; | |
| } catch (error) { | |
| console.error('Error extracting VCloud URL:', error); | |
| throw error; | |
| } | |
| } | |
| async function extractDownloadLinks(intermediateUrl: string): Promise<DownloadLink[]> { | |
| try { | |
| console.log(`Fetching download links from: ${intermediateUrl}`); | |
| // Add retry logic for download links too | |
| for (let attempt = 1; attempt <= 3; attempt++) { | |
| try { | |
| const response = await fetch(intermediateUrl, { | |
| headers: getRequestHeaders('https://vcloud.lol/'), | |
| redirect: 'follow', | |
| signal: AbortSignal.timeout(15000) | |
| }); | |
| if (!response.ok) { | |
| if (response.status === 403 && attempt < 3) { | |
| console.log(`Download links attempt ${attempt} failed with 403, retrying...`); | |
| await new Promise(resolve => setTimeout(resolve, 1000 * attempt)); | |
| continue; | |
| } | |
| throw new Error(`Failed to fetch download page: ${response.status}`); | |
| } | |
| const html: string = await response.text(); | |
| const downloadLinks: DownloadLink[] = []; | |
| // Extract 10Gbps Server link (btn-danger) | |
| const gbpsMatch = html.match(/<a[^>]+href="([^"]+)"[^>]*class="[^"]*btn[^"]*btn-danger[^"]*"[^>]*>[\s\S]*?Download[\s\S]*?\[Server\s*:\s*10Gbps\]/i); | |
| if (gbpsMatch && gbpsMatch[1]) { | |
| downloadLinks.push({ | |
| url: gbpsMatch[1], | |
| server: '10Gbps', | |
| type: 'download', | |
| resumeSupported: false, | |
| description: 'High-speed server (Resume not supported)' | |
| }); | |
| } | |
| // Extract Server 1 link (btn-success) | |
| const server1Match = html.match(/<a[^>]+href="([^"]+)"[^>]*class="[^"]*btn[^"]*btn-success[^"]*"[^>]*>[\s\S]*?Download[\s\S]*?\[Server\s*:\s*1\]/i); | |
| if (server1Match && server1Match[1]) { | |
| downloadLinks.push({ | |
| url: server1Match[1], | |
| server: 'Server 1', | |
| type: 'download', | |
| resumeSupported: true, | |
| description: 'Standard download server' | |
| }); | |
| } | |
| // Define URLs to exclude from results | |
| const excludedUrls: string[] = [ | |
| 'https://www.google.com/search?q=idm+dowload+manager', | |
| 'https://t.me/+_CVU9nIEj5oxYzc9' | |
| ]; | |
| // Define domains/patterns to exclude | |
| const excludedPatterns: string[] = [ | |
| 'google.com/search', | |
| 't.me/', | |
| 'telegram.me/', | |
| 'whatsapp.com', | |
| 'facebook.com', | |
| 'twitter.com', | |
| 'instagram.com' | |
| ]; | |
| // Extract any other download links | |
| const allLinkMatches: RegExpMatchArray | null = html.match(/<a[^>]+href="(https?:\/\/[^"]+)"[^>]*(?:class="[^"]*btn[^"]*")?[^>]*>[\s\S]*?(?:Download|download)/gi); | |
| if (allLinkMatches) { | |
| allLinkMatches.forEach((match: string, index: number) => { | |
| const urlMatch = match.match(/href="([^"]+)"/); | |
| if (urlMatch && urlMatch[1]) { | |
| const url: string = urlMatch[1]; | |
| // Skip if URL is in excluded list | |
| if (excludedUrls.includes(url)) { | |
| return; | |
| } | |
| // Skip if URL matches any excluded pattern | |
| if (excludedPatterns.some(pattern => url.includes(pattern))) { | |
| return; | |
| } | |
| // Skip if we already have this URL | |
| if (!downloadLinks.some((link: DownloadLink) => link.url === url)) { | |
| // Determine server type based on URL or content | |
| let serverType: string = 'Unknown'; | |
| let resumeSupported: boolean = true; | |
| if (url.includes('gpdl2.hubcdn.fans') || match.includes('10Gbps')) { | |
| serverType = '10Gbps'; | |
| resumeSupported = false; | |
| } else if (url.includes('pub-') && url.includes('.r2.dev')) { | |
| serverType = 'R2 CDN'; | |
| } else if (url.includes('ampproject.org')) { | |
| serverType = 'AMP Redirect'; | |
| resumeSupported = false; | |
| } | |
| // Only add if it's a valid download link | |
| if (serverType !== 'Unknown' || url.includes('download') || url.includes('.mkv') || url.includes('.mp4') || url.includes('.avi')) { | |
| downloadLinks.push({ | |
| url: url, | |
| server: serverType, | |
| type: 'download', | |
| resumeSupported: resumeSupported, | |
| description: `${serverType} download link` | |
| }); | |
| } | |
| } | |
| } | |
| }); | |
| } | |
| console.log(`Successfully extracted ${downloadLinks.length} download links`); | |
| return downloadLinks; | |
| } catch (fetchError) { | |
| if (attempt === 3) { | |
| throw fetchError; | |
| } | |
| console.log(`Download links attempt ${attempt} failed:`, fetchError); | |
| await new Promise(resolve => setTimeout(resolve, 1000 * attempt)); | |
| } | |
| } | |
| return []; | |
| } catch (error) { | |
| console.error('Error extracting download links:', error); | |
| throw error; | |
| } | |
| } | |
| export async function GET(request: NextRequest): Promise<NextResponse<VCloudResponse>> { | |
| try { | |
| // Validate API key | |
| const authResult = await validateApiKey(request); | |
| if (!authResult.isValid) { | |
| return createUnauthorizedResponse(authResult.error || 'Invalid API key') as NextResponse<VCloudResponse>; | |
| } | |
| const { searchParams } = new URL(request.url); | |
| const url: string | null = searchParams.get('url'); | |
| if (!url) { | |
| return NextResponse.json<VCloudResponse>( | |
| { | |
| success: false, | |
| error: 'URL parameter is required', | |
| usage: 'Use /api/vcloud?url=<vcloud_url>' | |
| }, | |
| { status: 400 } | |
| ); | |
| } | |
| // Validate that the URL is from VCloud | |
| try { | |
| const urlObj = new URL(url); | |
| const validDomains: string[] = ['vcloud.lol', 'vcloud.', 'v-cloud']; | |
| const isValidDomain: boolean = validDomains.some((domain: string) => | |
| urlObj.hostname.includes(domain) | |
| ); | |
| if (!isValidDomain) { | |
| return NextResponse.json<VCloudResponse>( | |
| { | |
| success: false, | |
| error: 'Invalid URL. Please provide a valid VCloud URL.', | |
| providedDomain: urlObj.hostname | |
| }, | |
| { status: 400 } | |
| ); | |
| } | |
| } catch (urlError) { | |
| return NextResponse.json<VCloudResponse>( | |
| { | |
| success: false, | |
| error: 'Invalid URL format provided' | |
| }, | |
| { status: 400 } | |
| ); | |
| } | |
| console.log('Processing VCloud URL:', url); | |
| // Step 1: Extract the intermediate URL | |
| const intermediateUrl: string | null = await extractVCloudUrl(url); | |
| if (!intermediateUrl) { | |
| return NextResponse.json<VCloudResponse>({ | |
| success: false, | |
| error: 'No intermediate URL found', | |
| message: 'Could not extract the intermediate URL from the VCloud page', | |
| remainingRequests: authResult.apiKey ? (authResult.apiKey.requestsLimit - authResult.apiKey.requestsUsed) : 0 | |
| }); | |
| } | |
| // Step 2: Extract the actual download links | |
| const downloadLinks: DownloadLink[] = await extractDownloadLinks(intermediateUrl); | |
| if (!downloadLinks || downloadLinks.length === 0) { | |
| return NextResponse.json<VCloudResponse>({ | |
| success: false, | |
| error: 'No download links found', | |
| message: 'Could not extract download links from the VCloud download page', | |
| remainingRequests: authResult.apiKey ? (authResult.apiKey.requestsLimit - authResult.apiKey.requestsUsed) : 0 | |
| }); | |
| } | |
| return NextResponse.json<VCloudResponse>({ | |
| success: true, | |
| originalUrl: url, | |
| downloadLinks: downloadLinks, | |
| totalLinks: downloadLinks.length, | |
| extractedAt: new Date().toISOString(), | |
| remainingRequests: authResult.apiKey ? (authResult.apiKey.requestsLimit - authResult.apiKey.requestsUsed) : 0 | |
| }); | |
| } catch (error: unknown) { | |
| console.error('VCloud API error:', error); | |
| return NextResponse.json<VCloudResponse>( | |
| { | |
| success: false, | |
| error: 'Failed to extract VCloud download links', | |
| details: error instanceof Error ? error.message : 'Unknown error occurred' | |
| }, | |
| { status: 500 } | |
| ); | |
| } | |
| } | |