const express = require('express'); const cors = require('cors'); const app = express(); const PORT = 7860; app.use(cors()); app.use(express.json()); function extractVideoId(url) { const match = url.match(/(?:youtube\.com\/(?:[^\/]+\/.+\/|(?:v|e(?:mbed)?)\/|.*[?&]v=)|youtu\.be\/)([^"&?\/\s]{11})/); return match ? match[1] : null; } function parseCaptionXml(xml) { const matches = xml.match(/]*>([^<]*)<\/text>/g); if (!matches) return ""; return matches .map(m => m.replace(/<[^>]+>/g, '')) .join(" ") .replace(/&#39;/g, "'") .replace(/&quot;/g, '"') .replace(/&amp;/g, "&") .replace(/'/g, "'") .replace(/\s+/g, ' ') .trim(); } // ======================================================== // KEEP-ALIVE ENDPOINT // ======================================================== app.get('/api/health', (req, res) => { res.json({ status: 'ok', message: 'Transcript API is running!' }); }); // ======================================================== // TRANSCRIPT API // ======================================================== app.post('/api/transcript', async (req, res) => { try { const { url } = req.body; const videoId = extractVideoId(url); if (!videoId) { return res.status(400).json({ error: "Invalid YouTube URL" }); } console.log(`Fetching InnerTube data for video_id: ${videoId}`); // Anti-bot headers and cookies to bypass YouTube blocks on Cloud IPs const headers = { "Content-Type": "application/json", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36", "Origin": "https://www.youtube.com", "Referer": "https://www.youtube.com/", "Cookie": "CONSENT=YES+1; SOCS=CAESHAgBEhJnd3NfMjAyMzEwMjUtMF9SQzIaAmVuIAEaBgiA-LaoBg" }; // 1. Fetch video player data from InnerTube API const playerRes = await fetch("https://www.youtube.com/youtubei/v1/player?prettyPrint=false", { method: "POST", headers: headers, body: JSON.stringify({ context: { client: { clientName: "WEB", clientVersion: "2.20241210.00.00", hl: "en", gl: "US" } }, videoId: videoId }) }); if (!playerRes.ok) { throw new Error(`YouTube API returned status ${playerRes.status}`); } const playerData = await playerRes.json(); // Check if video is playable if (playerData.playabilityStatus?.status === "ERROR") { return res.status(404).json({ error: playerData.playabilityStatus?.reason || "Video unavailable" }); } // 2. Extract caption tracks const captionTracks = playerData?.captions?.playerCaptionsTracklistRenderer?.captionTracks; if (!captionTracks || captionTracks.length === 0) { return res.status(404).json({ error: "No captions found for this video." }); } // Prefer English, fallback to first available let selectedTrack = captionTracks.find(t => t.languageCode === 'en') || captionTracks[0]; let captionUrl = selectedTrack.baseUrl; console.log(`Fetching captions from URL...`); // 3. Fetch the actual caption XML const captionRes = await fetch(captionUrl, { headers: { "User-Agent": headers["User-Agent"] } }); if (!captionRes.ok) { throw new Error(`Caption fetch failed with status ${captionRes.status}`); } const captionXml = await captionRes.text(); // 4. Parse the XML into plain text const fullText = parseCaptionXml(captionXml); if (!fullText) { return res.status(404).json({ error: "Caption XML was empty." }); } console.log(`Successfully fetched transcript. Length: ${fullText.length} chars`); res.json({ text: fullText }); } catch (error) { console.error("Transcript Error:", error.message); res.status(500).json({ error: "Could not fetch transcript. " + error.message }); } }); // ======================================================== // TRANSLATE API // ======================================================== app.post('/api/translate', async (req, res) => { try { const { text, targetLang } = req.body; if (!text || !targetLang) { return res.status(400).json({ error: "Missing text or target language" }); } const url = `https://translate.googleapis.com/translate_a/single?client=gtx&sl=auto&tl=${targetLang}&dt=t`; const fetchRes = await fetch(url, { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", }, body: `q=${encodeURIComponent(text)}`, }); if (!fetchRes.ok) { const errText = await fetchRes.text(); console.error("Google API Error:", errText); throw new Error(`Translation service responded with status ${fetchRes.status}`); } const data = await fetchRes.json(); const translated = data[0].map((item) => item[0]).join(""); res.json({ translatedText: translated }); } catch (error) { console.error("Translation API Error:", error.message); res.status(500).json({ error: "Failed to translate text. " + error.message }); } }); // Start server app.listen(PORT, '0.0.0.0', () => { console.log(`Server running on http://0.0.0.0:${PORT}`); });