twx-media-scrapper
Version:
Twitter/x.com media content extractor via Puppeteer and TwitterDL
169 lines (140 loc) âĸ 5.73 kB
JavaScript
const fs = require("fs");
const path = require("path");
const readline = require("readline");
const puppeteer = require("puppeteer");
const CACHE_PATH = path.join(__dirname, "auth_cache.json");
// Helper sleep
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
// CLI Prompt
const prompt = (question) => {
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
return new Promise(resolve => rl.question(question, answer => {
rl.close();
resolve(answer);
}));
};
async function getTwitterAuth(email, username, password) {
console.log("đ Mengecek cache login...");
if (fs.existsSync(CACHE_PATH)) {
const cache = JSON.parse(fs.readFileSync(CACHE_PATH, "utf-8"));
if (cache.authorization && cache.cookie) {
console.log("â
Menggunakan auth_cache.json (token dan cookie)");
return cache;
}
}
console.log("đ Membuka browser untuk login...");
const browser = await puppeteer.launch({
headless: true,
slowMo: 50,
args: ["--no-sandbox", "--disable-setuid-sandbox"]
});
const page = await browser.newPage();
await page.setUserAgent(
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) " +
"AppleWebKit/537.36 (KHTML, like Gecko) " +
"Chrome/123.0.0.0 Safari/537.36"
);
let bearerToken = null;
await page.setRequestInterception(true);
page.on("request", request => request.continue());
page.on("response", async response => {
const auth = response.request().headers()["authorization"];
if (auth && auth.startsWith("Bearer ") && !bearerToken) {
console.log("â
Bearer token ditemukan di response header");
bearerToken = auth;
}
});
try {
await page.goto("https://x.com/login", { waitUntil: "networkidle2", timeout: 60000 });
} catch (err) {
await browser.close();
throw new Error("â Gagal membuka halaman login Twitter.");
}
// Step 1: Email
console.log("đ Mengisi email...");
try {
await page.waitForSelector('input[name="text"]', { visible: true, timeout: 10000 });
await page.type('input[name="text"]', email);
await page.keyboard.press("Enter");
await sleep(2000);
} catch {
await browser.close();
throw new Error("â Input email tidak ditemukan. Gagal login.");
}
// Step 2: Username atau OTP
let nextInputHandled = false;
try {
await page.waitForSelector('input[name="text"]', { visible: true, timeout: 8000 });
try {
console.log("đ Mengisi username...");
await page.type('input[name="text"]', username);
await page.keyboard.press("Enter");
await sleep(2000);
nextInputHandled = true;
} catch {
console.log("â ī¸ Gagal isi username. Cek apakah ini OTP...");
}
if (!nextInputHandled) {
console.log("đ Twitter mungkin meminta kode verifikasi (OTP)");
const code = await prompt("Masukkan OTP dari email/HP: ");
await page.type('input[name="text"]', code);
await page.keyboard.press("Enter");
console.log("đ¨ Mengirim kode OTP...");
await sleep(3000);
nextInputHandled = true;
}
} catch {
console.log("âšī¸ Tidak ada input tambahan, lanjut ke password...");
}
// Step 3: Password
console.log("đ Mengisi password...");
try {
await page.waitForSelector('input[name="password"]', { visible: true, timeout: 10000 });
await page.type('input[name="password"]', password);
await page.keyboard.press("Enter");
await sleep(2000);
} catch {
await browser.close();
throw new Error("â Gagal menemukan input password. Mungkin akun butuh verifikasi tambahan atau salah kredensial.");
}
// Step 4: Tunggu navigasi
console.log("đ Menunggu proses login...");
try {
await page.waitForNavigation({ waitUntil: "networkidle2", timeout: 20000 });
} catch {
console.warn("â ī¸ Timeout saat navigasi. Melanjutkan...");
}
// Step 5: OTP tambahan
const otpInput = await page.$('input[name="text"]');
if (otpInput) {
console.log("đ Twitter meminta kode verifikasi tambahan (OTP kedua)");
const code = await prompt("Masukkan kode OTP lanjutan: ");
await otpInput.type(code);
await page.keyboard.press("Enter");
console.log("đ¨ Mengirim kode...");
try {
await page.waitForNavigation({ waitUntil: "networkidle2", timeout: 15000 });
} catch {
console.warn("â ī¸ Timeout saat navigasi OTP. Melanjutkan...");
}
await sleep(3000);
}
// Step 6: Ambil token dan cookie
console.log("âŗ Menunggu Bearer Token...");
await sleep(5000);
const cookies = await page.cookies();
const cookieHeader = cookies.map(c => `${c.name}=${c.value}`).join("; ");
await browser.close();
if (!bearerToken) {
console.error("â Bearer token tidak ditemukan.");
throw new Error("Gagal ambil Bearer token.");
}
const authData = {
authorization: bearerToken,
cookie: cookieHeader
};
fs.writeFileSync(CACHE_PATH, JSON.stringify(authData, null, 2));
console.log("â
Token disimpan ke auth_cache.json");
return authData;
}
module.exports = { getTwitterAuth };