File size: 3,042 Bytes
494fa67
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
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 });
  }
};