Loading snippets...
/*
γ Video FB Downloaderγ
Creator : ShanMolvyr
Base : https://en.getfvid.io/
Desc : Facebook Video Downloader, mengunduh video dan audio lengkap dengan metadata
Channel : https://whatsapp.com/channel/0029VbB4Kw8EFeXfeExaXc3Q
Note : 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 sini
const 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);