cutw
shortlink ads cutw bypass or solver get final url support all domain maybe
11 Oktober 2026412 file
cutw.jsjavascript · 216 baris
#!/usr/bin/env node/** * CutWin Final URL Resolver (curl backend) * * node cutw.js <url> * node cutw.js <url> --raw # JSON 1 baris * node cutw.js <url> --wait=2000 # jeda sebelum POST (ms) */import { execFile } from 'node:child_process';import { promisify } from 'node:util';import { mkdtemp, readFile, rm } from 'node:fs/promises';import { tmpdir } from 'node:os';import { join } from 'node:path';const execFileAsync = promisify(execFile);const SUPPORTED_DOMAINS = [ 'cutw.in', 'cutwin.com', 'cutwin.net', 'cutwin.org', 'cutwin.cz', 'www.cutw.in', 'www.cutwin.com', 'www.cutwin.net', 'www.cutwin.org', 'www.cutwin.cz',];const UA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36';function sleep(ms) { return new Promise((r) => setTimeout(r, ms));}/** * @param {string} shortUrl * @param {{ wait?: number }} [opts] */export async function resolveCutw(shortUrl, opts = {}) { const waitMs = opts.wait ?? 0; const startedAt = Date.now(); const result = { success: false, input: shortUrl, finalUrl: null, intermediateUrl: null, domain: null, flowId: null, durationMs: 0, error: null, timestamp: new Date().toISOString(), }; let tmpDir = null; try { let parsed; try { parsed = new URL(String(shortUrl).trim()); } catch { throw new Error('URL tidak valid'); } const host = parsed.hostname.toLowerCase(); if (!SUPPORTED_DOMAINS.includes(host)) { throw new Error( `Domain tidak didukung: ${host}. Didukung: cutw.in, cutwin.com, cutwin.net, cutwin.org, cutwin.cz` ); } result.domain = host; try { await execFileAsync('curl', ['--version']); } catch { throw new Error('curl tidak ditemukan. Install curl dulu.'); } tmpDir = await mkdtemp(join(tmpdir(), 'cutw-')); const cookieFile = join(tmpDir, 'cookies.txt'); const htmlFile = join(tmpDir, 'page.html'); // 1. GET short link const { stdout: finalUrlOut } = await execFileAsync( 'curl', [ '-sL', '-A', UA, '-H', 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8', '-H', 'Accept-Language: en-US,en;q=0.9', '-H', 'Sec-Fetch-Dest: document', '-H', 'Sec-Fetch-Mode: navigate', '-H', 'Sec-Fetch-Site: none', '-H', 'Sec-Fetch-User: ?1', '-H', 'Upgrade-Insecure-Requests: 1', '-c', cookieFile, '-b', cookieFile, '-o', htmlFile, '-w', '%{url_effective}', '--max-time', '20', '--connect-timeout', '10', parsed.href, ], { maxBuffer: 5 * 1024 * 1024 } ); const intermediateUrl = finalUrlOut.trim(); result.intermediateUrl = intermediateUrl; const html = await readFile(htmlFile, 'utf8'); // 2. Extract tokens const csrfMatch = html.match(/adxCsrfToken\s*=\s*["']([^"']*)["']/i); const flowMatch = html.match(/adxFlowId\s*=\s*["']([^"']+)["']/i); let flowId = flowMatch?.[1] || null; try { if (!flowId) flowId = new URL(intermediateUrl).searchParams.get('flow'); } catch { /* ignore */ } if (!flowId) { throw new Error('Tidak menemukan flowId. Struktur halaman mungkin berubah.'); } const csrfToken = csrfMatch ? csrfMatch[1].replace(/\\\//g, '/') : ''; result.flowId = flowId; // 3. Optional wait if (waitMs > 0) await sleep(waitMs); // 4. POST get_link const origin = new URL(intermediateUrl).origin; const { stdout: postOut } = await execFileAsync( 'curl', [ '-s', '-X', 'POST', '-A', UA, '-H', 'Accept: */*', '-H', 'Accept-Language: en-US,en;q=0.9', '-H', 'Content-Type: application/x-www-form-urlencoded; charset=UTF-8', '-H', 'X-Requested-With: XMLHttpRequest', '-H', `Origin: ${origin}`, '-H', `Referer: ${intermediateUrl}`, '-H', 'Sec-Fetch-Dest: empty', '-H', 'Sec-Fetch-Mode: cors', '-H', 'Sec-Fetch-Site: same-origin', '-b', cookieFile, '-c', cookieFile, '--data-urlencode', '_method=POST', '--data-urlencode', 'ajax=get_link', '--data-urlencode', `_csrfToken=${csrfToken}`, '--data-urlencode', `flow=${flowId}`, '--max-time', '15', '--connect-timeout', '10', intermediateUrl, ], { maxBuffer: 2 * 1024 * 1024 } ); let data; try { data = JSON.parse(postOut.replace(/^\uFEFF/, '').trim()); } catch { throw new Error(`Response bukan JSON: ${postOut.slice(0, 200)}`); } if (data?.status === 'success' && data?.url) { result.success = true; result.finalUrl = data.url; } else { throw new Error( data?.message || data?.debug || (data ? JSON.stringify(data) : 'Gagal get_link') ); } } catch (err) { result.success = false; result.error = err.message || String(err); } finally { result.durationMs = Date.now() - startedAt; if (tmpDir) { try { await rm(tmpDir, { recursive: true, force: true }); } catch { /* ignore */ } } } return result;}export { SUPPORTED_DOMAINS };// ── CLI ──────────────────────────────────────────────────────────async function main() { const args = process.argv.slice(2); // Pretty = default. Pakai --raw untuk 1 baris const raw = args.includes('--raw') || args.includes('-r'); const waitArg = args.find((a) => a.startsWith('--wait=')); const wait = waitArg ? Number(waitArg.split('=')[1]) : 0; const input = args.find((a) => !a.startsWith('-')); if (!input) { console.log(JSON.stringify({ success: false, error: 'URL short link diperlukan', usage: 'node cutw.js <url> [--raw|-r] [--wait=0]', examples: [ 'node cutw.js https://cutw.in/rPe73P', 'node cutw.js https://cutw.in/rPe73P --raw', 'node cutw.js https://cutw.in/rPe73P --wait=2000', ], supportedDomains: ['cutw.in', 'cutwin.com', 'cutwin.net', 'cutwin.org', 'cutwin.cz'], }, null, 2)); process.exit(1); } const result = await resolveCutw(input, { wait }); console.log(JSON.stringify(result, null, raw ? 0 : 2)); process.exit(result.success ? 0 : 1);}main();README.mdmarkdown · 139 baris
Komentar
Masuk untuk ikut berkomentar.
Belum ada komentar.