Spaces:
No application file
No application file
| import type { APIRoute } from 'astro'; | |
| import { extractionCache, CACHE_TTL } from '../../lib/cache'; | |
| 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 (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 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36' | |
| ]; | |
| export const POST: APIRoute = async ({ request }) => { | |
| try { | |
| const { url, format } = await request.json(); | |
| if (!url) { | |
| return new Response(JSON.stringify({ error: 'Subtitle URL is required' }), { status: 400 }); | |
| } | |
| // Check cache | |
| const cacheKey = `transcript-content-${url}-${format}`; | |
| if (extractionCache.has(cacheKey)) { | |
| const cached = extractionCache.get(cacheKey); | |
| if (Date.now() - cached.timestamp < CACHE_TTL) { | |
| return new Response(JSON.stringify(cached.data), { | |
| status: 200, | |
| headers: { 'Content-Type': 'application/json', 'X-Cache': 'HIT' } | |
| }); | |
| } | |
| } | |
| const ua = USER_AGENTS[Math.floor(Math.random() * USER_AGENTS.length)]; | |
| const response = await fetch(url, { | |
| headers: { | |
| 'User-Agent': ua, | |
| 'Accept': '*/*', | |
| 'Accept-Language': 'en-US,en;q=0.9', | |
| 'Referer': 'https://www.youtube.com/', | |
| 'Origin': 'https://www.youtube.com' | |
| } | |
| }); | |
| if (response.status === 429) { | |
| return new Response(JSON.stringify({ error: 'YouTube is rate-limiting transcript extraction. Please try again in a few minutes or try a different video.' }), { status: 429 }); | |
| } | |
| if (!response.ok) { | |
| throw new Error(`Failed to fetch subtitle: ${response.statusText} (${response.status})`); | |
| } | |
| let content = await response.text(); | |
| if (format === 'plain') { | |
| try { | |
| const json = JSON.parse(content); | |
| if (json.events) { | |
| content = json.events | |
| .map((e: any) => e.segs ? e.segs.map((s: any) => s.utf8).join('') : '') | |
| .filter((t: string) => t.trim().length > 0) | |
| .join(' ') | |
| .replace(/\s+/g, ' '); | |
| } | |
| } catch (e) { | |
| // If it's not JSON (e.g. VTT/SRT), try a simple regex strip | |
| content = content | |
| .replace(/WEBVTT/g, '') | |
| .replace(/\d{2}:\d{2}:\d{2}\.\d{3} --> \d{2}:\d{2}:\d{2}\.\d{3}/g, '') | |
| .replace(/<[^>]*>/g, '') | |
| .replace(/^\d+$/gm, '') | |
| .replace(/\n+/g, ' ') | |
| .trim(); | |
| } | |
| } | |
| const responseData = { content }; | |
| extractionCache.set(cacheKey, { | |
| timestamp: Date.now(), | |
| data: responseData | |
| }); | |
| return new Response(JSON.stringify(responseData), { | |
| status: 200, | |
| headers: { 'Content-Type': 'application/json' } | |
| }); | |
| } catch (error: any) { | |
| return new Response(JSON.stringify({ error: error.message }), { status: 500 }); | |
| } | |
| }; | |