LOADING
Gatau, intinya snaptik support foto/videohd
#!/usr/bin/env node
'use strict';
// WM: Avril pemula
// saluran : https://whatsapp.com/channel/0029VbCnMvaEgGfOncmyD02j
const fs = require('fs');
const path = require('path');
const crypto = require('crypto');
const WM = 'Avril pemula';
const CHANNEL = 'https://whatsapp.com/channel/0029VbCnMvaEgGfOncmyD02j';
const BASE_URL = 'https://snaptik.app';
const UA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36';
const jar = new Map();
function parseSetCookie(headers) {
const raw = headers.getSetCookie
? headers.getSetCookie()
: (headers.get('set-cookie') ? [headers.get('set-cookie')] : []);
for (const c of raw) {
const [pair] = c.split(';');
const i = pair.indexOf('=');
if (i > 0) jar.set(pair.slice(0, i).trim(), pair.slice(i + 1).trim());
}
}
const baseHeaders = () => {
const h = {
'User-Agent': UA,
'Accept': 'application/json, text/plain, */*',
'Accept-Language': 'en-US,en;q=0.9',
'Origin': BASE_URL,
'Referer': BASE_URL + '/'
};
if (jar.size) h.Cookie = [...jar].map(([k, v]) => `${k}=${v}`).join('; ');
return h;
};
async function initSession() {
const res = await fetch(BASE_URL + '/', { headers: baseHeaders() });
parseSetCookie(res.headers);
}
async function fetchToken() {
const res = await fetch(`${BASE_URL}/api/token`, {
method: 'POST',
headers: {
...baseHeaders(),
'X-Requested-With': 'XMLHttpRequest',
'Content-Type': 'application/json'
}
});
parseSetCookie(res.headers);
if (!res.ok) throw new Error(`token ${res.status}`);
return res.json();
}
function decryptPuzzle(id, b64) {
const raw = Buffer.from(b64, 'base64');
const iv = raw.subarray(0, 16);
const ct = raw.subarray(16);
const key = crypto.createHash('sha256')
.update(`sn4pt1k_v3r1fy2026:${id}`)
.digest();
const d = crypto.createDecipheriv('aes-256-cbc', key, iv);
return JSON.parse(Buffer.concat([d.update(ct), d.final()]).toString('utf8'));
}
function solve(p) {
switch (p.t) {
case 'b': return ((p.a ^ p.b) >> p.s) & 255;
case 'r': return p.n.reduce((x, v) => x + v, 0) * 2 + 1;
case 'c': return p.w.charCodeAt(p.i) * p.m;
case 'm': return ((p.a + p.b) % 100) * p.c;
case 'n': return p.a * p.b + p.b * p.c + p.c * p.a - p.a;
default: throw new Error('unknown challenge ' + p.t);
}
}
async function buildVerify() {
const t = await fetchToken();
const p = decryptPuzzle(t.id, t.p);
return `${t.id}:${solve(p)}:${p._e}:${p._h}`;
}
async function extract(url, retry = true) {
const verify = await buildVerify();
const res = await fetch(`${BASE_URL}/api/extract?url=${encodeURIComponent(url)}`, {
headers: {
...baseHeaders(),
'X-Requested-With': 'XMLHttpRequest',
'X-Verify': verify
}
});
parseSetCookie(res.headers);
if (res.status === 403 && retry) {
console.log(`[${WM}] 403 → retry token baru...`);
return extract(url, false);
}
if (!res.ok) throw new Error(`extract ${res.status}`);
return res.json();
}
// ← FIX: terima downloadUrl, kirim sebagai query param &url=
async function resolveHD(hdPath, downloadUrl) {
const verify = await buildVerify();
let url = hdPath.startsWith('http') ? hdPath : BASE_URL + hdPath;
if (downloadUrl) {
url += `&url=${encodeURIComponent(downloadUrl)}`;
}
const res = await fetch(url, {
headers: {
...baseHeaders(),
'X-Requested-With': 'XMLHttpRequest',
'X-Verify': verify
}
});
parseSetCookie(res.headers);
if (!res.ok) return null;
const j = await res.json();
return j.url || j.downloadUrl || j.hd || null;
}
async function download(url, dest) {
const res = await fetch(url, { headers: baseHeaders() });
if (!res.ok) throw new Error(`download ${res.status}`);
const buf = Buffer.from(await res.arrayBuffer());
fs.writeFileSync(dest, buf);
return buf.length;
}
const sanitize = (s) => String(s).replace(/[^\w\-.]+/g, '_').slice(0, 80);
(async () => {
const input = process.argv[2];
const out = {
watermark: WM,
saluran: CHANNEL,
input: input || null,
success: false,
type: null,
id: null,
title: null,
author: null,
stats: null,
source: { downloadUrl: null, hdDownloadUrl: null, hdResolved: null },
files: [],
error: null
};
if (!input) {
console.error('Usage: node snaptik.js "<url>"');
console.log(JSON.stringify(out, null, 2));
process.exit(1);
}
try {
console.log(`[${WM}] scraping: ${input}`);
await initSession();
const raw = await extract(input);
const d = raw.data || raw;
out.id = d.id || null;
out.title = d.title || null;
out.author = d.author || null;
out.stats = d.stats || null;
out.source.downloadUrl = d.downloadUrl || null;
out.source.hdDownloadUrl = d.hdDownloadUrl || null;
const slide = d.type === 'image' || d.type === 'slideshow' ||
(Array.isArray(d.images) && d.images.length > 0);
out.type = slide ? 'image' : 'video';
const base = sanitize(`${d.author?.username || 'tiktok'}_${d.id || Date.now()}`);
const cwd = process.cwd();
if (!slide) {
let dl = d.downloadUrl;
let q = 'SD';
if (d.hdDownloadUrl) {
try {
const hd = await resolveHD(d.hdDownloadUrl, d.downloadUrl);
if (hd) { dl = hd; q = 'HD'; out.source.hdResolved = hd; }
} catch (e) {
console.log(`[${WM}] HD gagal (${e.message}) → SD`);
}
}
if (!dl) throw new Error('no downloadUrl');
const f = path.join(cwd, `${base}.mp4`);
const size = await download(dl, f);
out.success = true;
out.files.push({ type: 'video', quality: q, path: f, size });
console.log(`[${WM}] ✓ ${q} → ${path.basename(f)} (${(size / 1048576).toFixed(2)} MB)`);
} else {
const imgs = d.images || [];
const audio = d.music?.playUrl || d.music?.url || d.audioUrl || d.sound?.playUrl || null;
for (let i = 0; i < imgs.length; i++) {
const im = imgs[i];
const u = typeof im === 'string' ? im : (im.url || im.downloadUrl || im.thumbnail);
if (!u) continue;
const f = path.join(cwd, `${base}_${String(i + 1).padStart(2, '0')}.jpg`);
const size = await download(u, f);
out.files.push({ type: 'image', index: i + 1, path: f, size });
}
if (audio) {
const f = path.join(cwd, `${base}_audio.mp3`);
const size = await download(audio, f);
out.files.push({ type: 'audio', path: f, size });
}
out.success = out.files.length > 0;
console.log(`[${WM}] ✓ slideshow → ${out.files.length} file`);
}
} catch (e) {
out.error = e.message;
console.error(`[${WM}] ✗ ${e.message}`);
}
console.log(JSON.stringify(out, null, 2));
process.exit(out.success ? 0 : 1);
})();