LOADING
x / Twitter downloader support gambar dan video langsung dari sumbernya tanpa api
import axios from 'axios';
import * as cheerio from 'cheerio';
import fs from 'fs';
import path from 'path';
class XDownloader {
constructor() {
this.headers = {
'User-Agent': 'Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Mobile Safari/537.36',
'Accept-Language': 'id-ID,id;q=0.9,en;q=0.8',
};
}
async getInfo(url) {
try {
console.log('🔍 Mengambil data dari X/Twitter...');
const response = await axios.get(url, {
headers: this.headers,
maxRedirects: 10
});
const html = response.data;
const $ = cheerio.load(html);
const title = $('meta[property="og:title"]').attr('content') || $('title').text().trim();
const description = $('meta[property="og:description"]').attr('content');
const thumbnail = $('meta[property="og:image"]').attr('content');
let videoUrl = null;
let mediaType = 'text';
// Metode 1: og:video
videoUrl = $('meta[property="og:video"]').attr('content') ||
$('meta[property="og:video:url"]').attr('content');
// Metode 2: Cari di semua script (paling penting)
if (!videoUrl) {
$('script').each((_, el) => {
const content = $(el).html() || '';
// Regex untuk video Twitter/X
let match = content.match(/"video_url":"(https:\/\/[^"]+\.mp4[^"]*)"/) ||
content.match(/"(https:\/\/video\.twimg\.com\/[^"]+\.mp4[^"]*)"/) ||
content.match(/"content_url":"(https:\/\/[^"]+\.mp4[^"]*)"/);
if (match) {
videoUrl = match[1].replace(/\\u0026/g, '&');
}
// Jika tidak ketemu, coba cari amplify video
if (!videoUrl && content.includes('amplify_video')) {
const ampMatch = content.match(/"url":"(https:\/\/[^"]+amplify_video[^"]+\.mp4[^"]*)"/);
if (ampMatch) videoUrl = ampMatch[1].replace(/\\u0026/g, '&');
}
});
}
if (videoUrl) mediaType = 'video';
else if (thumbnail && (thumbnail.includes('media') || thumbnail.includes('twimg'))) {
mediaType = 'image';
}
const result = {
success: true,
platform: "X/Twitter",
title: title ? title.substring(0, 200) : "Twitter Post",
description: description || "",
videoUrl: videoUrl,
thumbnail: thumbnail,
mediaType: mediaType,
originalUrl: url,
timestamp: new Date().toISOString()
};
console.log('\n📊 INFO TWEET:');
console.log(JSON.stringify(result, null, 2));
return result;
} catch (error) {
console.error('❌ Error:', error.message);
const err = { success: false, error: error.message };
console.log('\n📊 INFO TWEET:');
console.log(JSON.stringify(err, null, 2));
return err;
}
}
async download(url) {
const info = await this.getInfo(url);
if (!info.success) return;
if (!info.videoUrl) {
console.log('\n❌ Tidak menemukan video di tweet ini (bisa jadi hanya gambar atau teks).');
return;
}
const outputDir = './downloads_x';
if (!fs.existsSync(outputDir)) fs.mkdirSync(outputDir, { recursive: true });
const cleanTitle = info.title.replace(/[^a-zA-Z0-9\s]/g, '').trim().substring(0, 60) || 'x_video';
const fileName = `${cleanTitle}.mp4`;
const outputPath = path.join(outputDir, fileName);
console.log(`\n⬇️ Downloading video...`);
console.log(`📁 ${outputPath}`);
try {
const response = await axios({
method: 'GET',
url: info.videoUrl,
responseType: 'stream',
headers: { 'User-Agent': this.headers['User-Agent'] }
});
const writer = fs.createWriteStream(outputPath);
response.data.pipe(writer);
await new Promise((resolve, reject) => {
writer.on('finish', () => console.log('✅ Download selesai!'));
writer.on('error', reject);
});
} catch (err) {
console.error('❌ Download gagal:', err.message);
}
}
}
// CLI
const args = process.argv.slice(2);
if (args.length === 0) {
console.log('🚀 Usage: node x-downloader.js <twitter-url>');
process.exit(1);
}
const downloader = new XDownloader();
downloader.download(args[0]);bashnpm i axios cheerio
bashnode x.js <url>