getfvid
Facebook Video Downloader dengan metadata
31 Mei 2026381 file
getvid.jsjavascript · 220 baris
/*【 Video FB Downloader】Creator : ShanMolvyrBase : https://en.getfvid.io/Desc : Facebook Video Downloader, mengunduh video dan audio lengkap dengan metadataChannel : https://whatsapp.com/channel/0029VbB4Kw8EFeXfeExaXc3QNote : Kalau error biasanya dari api nya, di tes dulu aja*/import axios from 'axios';import FormData from 'form-data';import WebSocket from 'ws';const BASE_URL = 'https://en.getfvid.io';const CDN_URL = 'https://cdn.rapidcdn.site';const WSS_BASE = 'wss://cdn.rapidcdn.site/ws/status';const DEFAULT_HEADERS = { 'Origin': BASE_URL, 'Referer': `${BASE_URL}/`, 'User-Agent': 'Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36', 'Accept-Language': 'id-ID,id;q=0.9,en-US;q=0.8,en;q=0.7',};// urutan prioritas — fake_scale difilter dulu, baru fallback ke siniconst VIDEO_QUALITY_RANK = ['1080p', '800p', '720p', '640p', '536p', '540p', '480p', 'HD', '360p', '240p', 'SD'];const AUDIO_QUALITY_RANK = ['bestaudio', '320kbps', '256kbps', '192kbps', '128kbps', 'm4a'];function parseTitle(raw) { const metaMatch = raw.match(/^([\d.KMB]+\s*reactions\s*[·•]\s*[\d.KMB]+\s*shares?)\s*\|\s*/i); const metaRaw = metaMatch ? metaMatch[1] : null; let rest = metaMatch ? raw.slice(metaMatch[0].length) : raw; const parts = rest.split('|').map(s => s.trim()).filter(Boolean); const uploader = parts.length > 1 ? parts[parts.length - 1] : null; const title = parts.length > 1 ? parts.slice(0, -1).join(' | ').trim() : parts[0]; let reactions = null, shares = null; if (metaRaw) { const rMatch = metaRaw.match(/([\d.KMB]+)\s*reactions/i); const sMatch = metaRaw.match(/([\d.KMB]+)\s*shares?/i); reactions = rMatch ? rMatch[1] : null; shares = sMatch ? sMatch[1] : null; } return { title, uploader, reactions, shares };}export async function fetchVideoInfo(fbUrl, cookie = '') { const fd = new FormData(); fd.append('url', fbUrl); const res = await axios.post(`${BASE_URL}/api.php`, fd, { headers: { ...DEFAULT_HEADERS, ...fd.getHeaders(), ...(cookie ? { Cookie: cookie } : {}), }, }); if (res.data.status !== 'success') { throw new Error(`api.php error: ${JSON.stringify(res.data)}`); } return res.data;}export async function requestRender(fbUrl, formatId, cookie = '') { const fd = new FormData(); fd.append('url', fbUrl); fd.append('format_id', formatId); const res = await axios.post(`${BASE_URL}/render.php`, fd, { headers: { ...DEFAULT_HEADERS, ...fd.getHeaders(), ...(cookie ? { Cookie: cookie } : {}), }, }); return res.data;}export async function waitRender(taskId, timeoutMs = 180_000) { return new Promise((resolve, reject) => { const ws = new WebSocket(`${WSS_BASE}/${taskId}`, { headers: { 'Origin': BASE_URL }, }); const timer = setTimeout(() => { ws.close(); reject(new Error('Render timeout')); }, timeoutMs); ws.on('message', (raw) => { const msg = JSON.parse(raw.toString()); if (msg.progress !== undefined) process.stdout.write(`\rProgress: ${msg.progress}% `); if (msg.status === 'completed') { clearTimeout(timer); ws.close(); process.stdout.write('\n'); resolve(msg); } else if (msg.status === 'failed') { clearTimeout(timer); ws.close(); reject(new Error(`Render failed: ${msg.error}`)); } }); ws.on('error', (err) => { clearTimeout(timer); reject(err); }); });}export function buildDownloadUrl(wsResult) { const token = Buffer.from(JSON.stringify({ url: `${CDN_URL}/${wsResult.file_path}`, filename: wsResult.display_name, })).toString('base64'); return `${BASE_URL}/dl.php?token=${token}`;}async function resolveLink(fbUrl, link, cookie) { if (!link.is_render) return link.url; const renderRes = await requestRender(fbUrl, link.format_id, cookie); const wsResult = await waitRender(renderRes.task_id); return buildDownloadUrl(wsResult);}/** * Ambil kualitas terbaik otomatis * @returns {object} { video, audio, meta } */export async function getBest(fbUrl, { cookie = '', audioOnly = false } = {}) { const res = await fetchVideoInfo(fbUrl, cookie); const links = res.download_links; const raw = res.data; // parse metadata dari title const parsed = parseTitle(raw.title); const meta = { id: raw.id, title: parsed.title, uploader: parsed.uploader ?? raw.uploader, reactions: parsed.reactions, shares: parsed.shares, duration: raw.duration, views: raw.views, thumbnail: raw.thumbnail, }; // pilih video terbaik — prefer non-fake-scale, fallback ke fake-scale kalau tidak ada let videoResult = null; if (!audioOnly) { const realLinks = links.filter(l => !l.is_fake_scale); const bestVideoQuality = VIDEO_QUALITY_RANK.find(q => realLinks.some(l => l.quality === q)) || VIDEO_QUALITY_RANK.find(q => links.some(l => l.quality === q)); const videoLink = links.find(l => l.quality === bestVideoQuality && !l.is_fake_scale) ?? links.find(l => l.quality === bestVideoQuality); const fakeNote = videoLink?.is_fake_scale ? " (upscale)" : ""; if (videoLink) { try { console.log(`Video terbaik: ${videoLink.quality}${fakeNote} (${videoLink.is_render ? "render" : "direct"})`); const url = await resolveLink(fbUrl, videoLink, cookie); videoResult = { quality: videoLink.quality, isFakeScale: !!videoLink.is_fake_scale, url }; } catch (err) { console.warn(`Video ${videoLink.quality} gagal: ${err.message}, fallback ke HD direct...`); const hdLink = links.find(l => l.quality === 'HD'); if (hdLink) { const url = await resolveLink(fbUrl, hdLink, cookie); videoResult = { quality: 'HD', isFakeScale: false, url }; } } } } // pilih audio terbaik dengan fallback kalau render gagal const audioLinks = AUDIO_QUALITY_RANK .map(q => links.find(l => l.quality === q)) .filter(Boolean); let audioResult = null; for (const audioLink of audioLinks) { try { console.log(`Audio terbaik: ${audioLink.quality} (${audioLink.is_render ? 'render' : 'direct'})`); const url = await resolveLink(fbUrl, audioLink, cookie); audioResult = { quality: audioLink.quality, url }; break; } catch (err) { console.warn(`Audio ${audioLink.quality} gagal: ${err.message}, coba format berikutnya...`); } } return { video: videoResult, audio: audioResult, meta };}// ─── RUN ─────────────────────────────────────────────────────────────────────const FB_URL = 'https://www.facebook.com/share/r/15qZVepkZX7/';const result = await getBest(FB_URL);console.log('\n─── METADATA ───────────────────────────────');console.log('Title :', result.meta.title);console.log('Uploader :', result.meta.uploader);console.log('Reactions:', result.meta.reactions);console.log('Shares :', result.meta.shares);console.log('Duration :', result.meta.duration + 's');console.log('Views :', result.meta.views);console.log('Thumbnail:', result.meta.thumbnail);console.log('\n─── VIDEO ──────────────────────────────────');console.log('Quality :', result.video.quality);console.log('URL :', result.video.url);console.log('\n─── AUDIO ──────────────────────────────────');console.log('Quality :', result.audio.quality);console.log('URL :', result.audio.url);Komentar
Masuk untuk ikut berkomentar.
Belum ada komentar.