Cara Memecahkan ALTCHA di Node.js dan Mengirimkannya dengan Fetch

Anda bisa memecahkan ALTCHA di Node.js dengan satu pemanggilan dan tanpa browser sama sekali di dalam stack. ALTCHA adalah proof of work, bukan pengenalan gambar: situs menerbitkan sebuah challenge, dan klien harus melakukan hashing sampai menemukan counter yang memenuhinya. Tidak ada yang perlu dilihat, jadi tidak ada WebDriver, tidak ada headless Chrome, dan tidak ada user agent yang terlibat, serta jawabannya dihitung, bukan ditebak. CapSkip menambahkan tipe ini pada versi 1.2.6 dan Node SDK menyediakannya sebagai satu metode tunggal. Itulah yang membuatnya menjadi tipe CAPTCHA langka yang seluruh prosesnya berupa skrip HTTP biasa: ambil halamannya, baca challenge dari halaman itu, pecahkan, kirim token kembali, semuanya dengan global fetch dan satu pemanggilan SDK.
Apa yang Anda butuhkan
- CapSkip 1.2.6 atau lebih baru yang berjalan di komputer Windows. Dukungan ALTCHA hadir pada rilis tersebut.
- Node 18 atau yang lebih baru, yang memang disyaratkan paket ini dan sekaligus menjadi sumber global fetch yang dipakai di bawah ini. Definisi TypeScript sudah disertakan di dalam paket, jadi tidak ada paket types yang perlu diinstal terpisah.
- URL halaman tempat widget berada, dan endpoint yang diminta widget itu untuk mengambil challenge-nya.
- Alamat untuk solver. Local mode merespons di 127.0.0.1 hanya untuk perangkat itu saja; Server mode mendengarkan di alamat jaringan atau IP publik Anda sehingga mesin lain bisa menjangkaunya. Langkah 4 membahas mana yang berlaku, dan keduanya berada di bawah pengaturan koneksi.
# npm install capskip npm install capskip
Langkah 1: pemanggilan solve, dan dari mana challenge berasal
Satu metode, dua argumen: URL halaman, lalu objek options yang membawa challenge. Berikan endpoint-nya dan CapSkip akan mengambil sendiri challenge tersebut.
// npm install capskip
const { CapSkip } = require('capskip');
const solver = new CapSkip({ host: '127.0.0.1', port: 8080 });
// CapSkip fetches the challenge, then hashes until the counter fits.
const result = await solver.altcha('https://example.com/signup', {
challengeUrl: 'https://example.com/altcha/challenge',
});
console.log(result.token); // base64 payload for the form field
console.log(result.number); // the counter that satisfied itDua field pada hasil hanya dimiliki ALTCHA. token adalah payload base64 yang diminta formulir, dan number adalah counter yang memecahkan challenge tersebut. Field code membawa string yang sama dengan token, jadi keduanya sama-sama bisa dipakai, tetapi nama token sesuai dengan field tujuannya dan lebih enak dibaca di titik pemanggilan. Field GeeTest dan user agent Turnstile tetap tidak ada di sini.
Nama option ini punya lebih dari satu ejaan yang diterima. Baik challengeUrl maupun challenge_url menuju parameter API yang sama, dan hal yang sama berlaku untuk challengeJson dan challenge_json. Ejaan camel case adalah yang dipakai dokumentasi Node dan yang selaras dengan bagian lain SDK, jadi utamakan ejaan itu dan tetaplah konsisten; alias snake case ada supaya contoh yang disalin dari panduan PHP atau Python tetap bisa dijalankan.
Temukan endpoint yang diminta widget
Buka DevTools, masuk ke tab Network, lalu muat ulang halaman tempat widget berada. Widget membuat satu request untuk mengambil challenge-nya, biasanya ke path yang mengandung altcha. URL request itulah yang Anda berikan, dan JSON yang dikembalikannya adalah dokumen challenge, yang bisa Anda berikan sebagai gantinya.
Jangan menebak atribut yang menamainya, karena atribut itu berubah antar generasi widget. Baca kode sumber halaman.
| Generasi widget | Atribut yang menamai challenge |
|---|---|
| v1 dan v2 | challengeurl untuk endpoint, dengan atribut challengejson terpisah untuk challenge inline |
| v3 dan setelahnya | challenge, dan atribut yang sama itu menerima URL atau data challenge |
<!-- v1 and v2 name the endpoint on its own attribute --> <altcha-widget challengeurl="https://example.com/altcha/challenge"></altcha-widget> <!-- v3 and later put both forms behind one attribute --> <altcha-widget challenge="https://example.com/altcha/challenge"></altcha-widget>
Ketiga gaya tampilannya, native, checkbox, dan switch, murni bersifat visual. Semuanya mengirim payload yang sama dan perbedaannya tidak pernah sampai ke solver, jadi Anda tidak perlu menebak mana yang sedang Anda hadapi. ALTCHA mendokumentasikan atributnya di panduan integrasinya sendiri.
Memberikan dokumen challenge sebagai gantinya
Jika scraper Anda sudah membaca challenge dari halaman, berikan dokumennya dan tidak ada request jaringan yang terjadi sama sekali.
// No fetch happens: the document is already here.
const result = await solver.altcha('https://example.com/signup', {
challengeJson: {
algorithm: 'SHA-256',
challenge: 'YOUR_CHALLENGE_HASH',
salt: 'YOUR_SALT',
signature: 'YOUR_SIGNATURE',
maxnumber: 1000000,
},
});Option itu menerima sebuah objek, yang akan diserialisasi untuk Anda, atau sebuah string JSON jika Anda sudah memilikinya. Mengirim endpoint dan dokumen sekaligus diperbolehkan dan dokumen inline yang menang, karena pengambilan hanya akan mengulang apa yang baru saja Anda berikan. Namun kedua jalur ini berperilaku berbeda saat beban tinggi. Challenge inline yang sudah kedaluwarsa langsung ditolak alih-alih di-hash sia-sia, sedangkan endpoint memungkinkan solver mengambil challenge baru jika challenge pertama sudah mati selagi job menunggu di queue.
Algoritma apa saja yang dicakup pemecah CAPTCHA
Metode yang sama menangani kedua generasi. Skema generasi lama tercakup dengan SHA-1, SHA-256, SHA-384, dan SHA-512, sedangkan proof of work v2 tercakup dengan PBKDF2 dan SHA iteratif. PBKDF2 adalah default yang direkomendasikan ALTCHA sendiri, jadi cakupan itu meliputi sebagian besar situs yang aktif.
Argon2id dan scrypt adalah pengecualian, dan keduanya ditolak alih-alih dicoba: task yang memakai salah satunya kembali dalam sekitar sepertiga detik dengan ERROR_CAPTCHA_UNSOLVABLE dan tidak pernah dicoba ulang. Itu memang disengaja. Fungsi yang berat di memori bukan masalah yang bisa diperbaiki oleh percobaan ulang, jadi gagal seketika lebih baik daripada terlihat sibuk. Untuk ALTCHA, hasil itu menunjuk pada algoritmanya, bukan pada gambar yang tidak terbaca.
Langkah 2: jalankan seluruh prosesnya dengan fetch, tanpa browser
Karena tidak ada yang perlu dirender, halaman tempat Anda mengambil challenge hanyalah sebuah dokumen yang bisa Anda fetch. Ini perlu dikatakan terus terang, karena untuk setiap tipe CAPTCHA widget jawaban jujurnya selalu melibatkan browser di suatu tempat. Di sini tidak. Ambil halamannya, tarik atributnya dari markup, lalu berikan langsung ke solver.
// npm install capskip
const PAGE = 'https://example.com/signup';
// The page is only a document here: no browser, no rendering.
const html = await (await fetch(PAGE)).text();
// v1 and v2 use challengeurl; v3 and later use challenge.
const found = html.match(/(?:challengeurl|challenge)="([^"]+)"/i);
if (!found) throw new Error('no ALTCHA widget on this page');
const result = await solver.altcha(PAGE, { challengeUrl: found[1] });Regular expression cukup untuk satu halaman yang sudah Anda kenal dan merupakan ide buruk untuk crawler, jadi pakailah parser HTML sungguhan begitu Anda menangani markup yang bukan buatan Anda. Inti dari contoh ini adalah bentuk alurnya, bukan cara parsing-nya: satu request, satu string, satu pemecahan, dan tidak ada proses yang harus dijalankan atau dimatikan. Itu juga sebabnya tipe ini bekerja baik di dalam serverless function atau worker berumur pendek, yang biaya menjalankan Chromium-nya jauh melampaui biaya pemecahan itu sendiri.
Satu catatan tentang atribut v3. Isinya bisa berupa URL atau dokumen challenge itu sendiri, jadi periksa dulu mana yang Anda dapat sebelum memberikannya. Jika nilainya diawali kurung kurawal, bukan skema URL, itu adalah challenge inline, dan tempatnya justru pada option dokumen dari bagian sebelumnya.
Memberi tipe pada hasil, jika Anda memakai TypeScript
Definisi tipe sudah disertakan di dalam paket, jadi tidak ada paket types yang perlu diinstal. Satu tipe hasil mencakup semua tipe CAPTCHA yang dipecahkan SDK, yang berarti setiap field yang hanya dimiliki salah satu tipe dideklarasikan sebagai opsional. token dan number adalah field ALTCHA, jadi compiler memberi token tipe string atau undefined dan tidak akan mengizinkan Anda menyerahkannya ke sesuatu yang mengharapkan string biasa.
// npm install capskip
import { CapSkip, SolveResult, AltchaOptions } from 'capskip';
const options: AltchaOptions = { challengeUrl: found[1] };
const result: SolveResult = await solver.altcha(PAGE, options);
// One check, right after the call, and the type is settled.
if (!result.token) throw new Error('no ALTCHA token on this result');
const token: string = result.token;Itu dorongan yang sama seperti yang diberikan user agent Turnstile di panduan Turnstile Node.js, dengan konsekuensi yang lebih tajam: user agent yang hilang membuat pengiriman Anda ditolak, sedangkan token yang hilang berarti Anda tidak punya apa pun untuk dikirim. Pakai non-null assertion hanya jika Anda benar-benar yakin, karena hal itu membungkam satu-satunya pemeriksaan yang memberi tahu Anda bahwa metode yang dipanggil salah.
Satu hal yang tidak akan ditangkap oleh tipe. Interface options memiliki index signature, sehingga setiap key tambahan yang Anda tulis diterima compiler. Karena itu option yang salah eja tetap lolos build dan baru gagal saat dijalankan, sebab SDK menolak parameter yang tidak diterima ALTCHA. Memberi anotasi pada objek options, seperti di atas, setidaknya memeriksa key yang memang dikenalinya.
Langkah 3: kirim token kembali tanpa perubahan, sebelum kedaluwarsa
Widget mengirim payload-nya di field form bernama altcha, jadi di situlah token Anda ditempatkan. Inilah langkah yang gagal tanpa memberi tanda.
// Send it exactly as it came back: no trimming,
// no re-encoding, no reordering.
const response = await fetch('https://example.com/signup', {
method: 'POST',
body: new URLSearchParams({
email: '[email protected]',
altcha: token,
}),
});Token adalah base64 dari dokumen JSON yang field-nya dicakup oleh signature HMAC milik server itu sendiri. Modifikasi apa pun membuatnya tidak valid, jadi apa pun yang tampak seperti merapikan akan merusak pengiriman: memangkas spasi, mendekode dan mengodekan ulang, atau membangun ulang JSON dengan urutan key yang berbeda. Sebagian integrasi membaca payload dari field body JSON bukan dari field form, jadi periksa apa yang dikirim oleh submit halaman itu sendiri dan tiru itu.
Cara lain langkah ini gagal adalah soal waktu. Jendela challenge itu pendek dan sebagian situs menutupnya dalam dua menit. Ketika satu challenge kedaluwarsa, situs menolak jawabannya dengan kegagalan verifikasi polos yang terlihat persis seperti jawaban salah, dan tidak ada apa pun dalam respons yang memberi tahu Anda mana dari keduanya yang terjadi. Tiga kebiasaan ini menghindarinya: ambil challenge tepat sebelum memecahkannya, bukan di awal proses yang panjang; kirim token dalam unit kerja yang sama dengan yang memecahkannya; dan jangan pernah menyimpan token selagi seseorang mengisi formulir.
Polling timeout milik klien bukanlah yang membatasi Anda, karena jendela challenge sudah tertutup jauh sebelum salah satu dari keduanya tercapai. ALTCHA adalah pekerjaan CPU, bukan sesi browser, jadi ia berjalan pada polling timeout default dan bukan pada timeout reCAPTCHA yang lebih panjang.
| Opsi constructor | Bawaan | Apa yang dicakupnya |
|---|---|---|
| defaultTimeout | 120 detik | Polling ALTCHA dan CAPTCHA gambar |
| recaptchaTimeout | 300 detik | Polling reCAPTCHA, Turnstile dan GeeTest |
| pollingInterval | Maksimum 5 detik | Polling dimulai pada 0,25 detik dan melambat hingga nilai ini |
Langkah 4: di mana pemecah CAPTCHA berjalan, dan mode koneksi apa yang dibutuhkan
Contoh di atas memakai 127.0.0.1 karena itulah yang tepat ketika proses Node Anda dan solver berada di satu mesin. Begitu kode pemanggilnya berjalan di tempat lain, misalnya di container, CI runner, VPS, atau hosting terkelola, loopback tidak lagi menunjuk ke solver, dan pemecahan pertama langsung ditolak dengan NetworkException.
Alihkan CapSkip ke Server mode dan ia akan mendengarkan di alamat jaringan atau IP publik Anda, sehingga semua tempat tadi bisa menjangkaunya lewat HTTP API yang sama. IP publik statis disarankan bila jalurnya melewati internet, disertai aturan firewall yang hanya mengizinkan alamat yang Anda harapkan. Server mode hanya mengubah di mana solver mendengarkan dan tidak lebih dari itu: perangkat kerasnya tetap milik Anda, dan tetap tanpa kuota. Baca host dan port dari environment supaya satu build bekerja di kedua tempat. Klien tidak membaca CAPSKIP_HOST atau CAPSKIP_PORT dengan sendirinya, jadi teruskan keduanya ke konstruktor, seperti yang dilakukan contoh lengkap di bawah.
| Tempat proses Node berjalan | Mode koneksi yang mana |
|---|---|
| Di mesin CapSkip, sebagai skrip atau server lokal | Mode Local. 127.0.0.1 memang benar |
| Di mesin lain pada jaringan yang sama | Mode Server, di alamat privat mesin tersebut |
| Di container, di VPS, atau di platform terkelola | Pakai mode Server dengan IP publik statis dan sebuah aturan firewall |
Satu catatan khusus ALTCHA soal proxy. Proxy didukung di sini, tetapi hanya dipakai untuk pengambilan challenge. Tidak ada sesi browser yang perlu dirutekan, jadi proxy tidak berpengaruh pada proof of work itu sendiri.
Contoh lengkap yang berfungsi
// npm install capskip
import { CapSkip, ApiException, TimeoutException, NetworkException } from 'capskip';
const solver = new CapSkip({
host: process.env.CAPSKIP_HOST || '127.0.0.1',
port: Number(process.env.CAPSKIP_PORT || 8080),
});
export async function signUp(email: string) {
try {
// Fetch, solve and submit in one unit of work.
const result = await solver.altcha('https://example.com/signup', {
challengeUrl: 'https://example.com/altcha/challenge',
});
if (!result.token) throw new Error('not an ALTCHA result');
const response = await fetch('https://example.com/signup', {
method: 'POST',
body: new URLSearchParams({ email, altcha: result.token }),
});
console.log(response.status, 'after counter', result.number);
} catch (err) {
// ERROR_CAPTCHA_UNSOLVABLE here means Argon2id or scrypt.
if (err instanceof ApiException) console.log('refused:', err.message);
else if (err instanceof TimeoutException) console.log('gave up waiting');
else if (err instanceof NetworkException) console.log('solver unreachable');
else throw err;
}
}Tipe lain berbentuk sama dengan metode yang berbeda. Pemanggilan reCAPTCHA menerima sitekey dan URL halaman, Turnstile bekerja dengan cara yang sama, GeeTest menerima nilai gt dan sebuah challenge di samping URL halaman, dan pemecahan gambar menerima path file, URL, atau base64. Daftar metode lengkapnya ada di halaman pemecah CAPTCHA Node.js, dan metode yang sama tersedia di setiap paket resmi di halaman SDK.
Turnstile adalah satu-satunya tipe yang butuh lebih dari sekadar sitekey ketika ia muncul sebagai halaman challenge penuh. Nilai tambahannya dibahas di panduan Turnstile Node.js.
Kesalahan umum dan artinya
| Apa yang Anda lihat | Penyebab | Perbaiki |
|---|---|---|
| Compiler menolak token, dengan pesan bahwa string atau undefined bukanlah string | Satu tipe hasil mencakup semua tipe CAPTCHA, jadi field khusus ALTCHA bersifat opsional | Persempit tipenya sekali setelah pemecahan, lalu pakai nilai yang sudah dipersempit itu |
| Option yang salah eja lolos build dan baru gagal saat dijalankan | Interface options memiliki index signature, sehingga key yang tidak dikenal tetap diloloskan | Beri anotasi objek options dengan tipe options ALTCHA dan periksa ejaannya |
| token terbaca undefined saat dijalankan | Field itu hanya terisi untuk ALTCHA | Panggil metode ALTCHA. Pada hasil ALTCHA, field code berisi string yang sama |
| Kegagalan verifikasi biasa dari situs, dengan token yang tampak baik-baik saja | Challenge kedaluwarsa sebelum form dikirim | Ambil, pecahkan dan kirim dalam satu unit kerja |
| ERROR_CAPTCHA_UNSOLVABLE di dalam ApiException, dalam sekitar sepertiga detik | Challenge memakai Argon2id atau scrypt | Tidak ada yang perlu dicoba ulang. Keduanya ditolak secara desain |
| ValidationException pada pemanggilan | Tidak ada opsi challenge yang diberikan, atau ada opsi yang diberikan tetapi tidak diterima ALTCHA | Berikan endpoint challenge atau dokumen challenge, dan buang yang lainnya |
| NetworkException pada pemecahan pertama | CapSkip tidak berjalan, atau host dan port-nya salah | Jalankan CapSkip, lalu periksa apakah ia harus berada di mode Local atau mode Server |
| Form menolak token yang menurut log Anda berhasil dipecahkan | Ada sesuatu yang mengodekan ulang, memangkas atau mengubah urutan payload | Teruskan string itu apa adanya, tanpa disentuh |
FAQ
Apakah saya perlu Puppeteer atau Playwright untuk halaman ALTCHA?
Tidak, dan justru itulah bagian yang berguna. ALTCHA memberikan soal hashing, bukan sesuatu untuk dilihat, jadi pekerjaannya murni CPU dan selesai dalam hitungan milidetik. Tidak ada browser, tidak ada WebDriver, dan tidak ada user agent yang terlibat. Skrip biasa dengan global fetch sudah cukup, yang juga berarti ia berjalan mulus di dalam worker, queue consumer, atau serverless function, tempat menjalankan Chromium justru lambat dan merepotkan.
Bisakah aplikasi Node di platform hosting menjangkau solver?
Bisa. Alihkan CapSkip ke Server mode di pengaturan koneksi supaya ia mendengarkan di alamat jaringan alih-alih loopback, lalu arahkan environment variable host ke alamat itu. Container, CI runner, VPS, dan platform aplikasi terkelola semuanya terhubung dengan cara yang sama, lewat HTTP API yang sama. Pakai IP publik statis jika jalurnya melintasi internet, dan batasi dengan aturan firewall. Solver tetap berada di perangkat keras milik Anda dalam setiap kasus tersebut, jadi tidak ada yang berubah pada lisensi maupun jumlah pemecahan.
Apakah klien async memecahkan beberapa challenge ALTCHA lebih cepat?
Tidak dengan sendirinya. Di paket Node, klien async hanyalah alias dari klien biasa, bukan implementasi kedua, jadi mengimpornya tidak mengubah apa pun tentang cara kerjanya. Setiap metode sudah mengembalikan promise, jadi konkurensi datang dari menjalankan beberapa metode sekaligus lalu menunggu semuanya selesai. Saat melakukannya, jaga agar setiap fetch tetap berdampingan dengan pemecahannya sendiri, karena setiap challenge kedaluwarsa secara terpisah dan sekumpulan challenge yang diambil di awal akan basi selagi beberapa yang pertama masih di-hash.
Apakah saya harus memakai TypeScript untuk memakai SDK ini?
Tidak. Definisi tipenya sudah disertakan di dalam paket, jadi ia ada kalau proyek Anda membacanya dan tidak terlihat kalau tidak. CommonJS biasa bekerja persis seperti pada contoh pertama, dan satu-satunya perbedaan adalah token opsional itu berubah menjadi pemeriksaan runtime yang Anda tulis sendiri, bukan yang dipaksakan compiler. Pemeriksaan itu layak ditulis dalam kedua kasus, karena token yang undefined adalah sinyal paling jelas bahwa metode yang dipanggil salah.
Versi singkatnya
Baca endpoint challenge dari widget, berikan ke satu metode ALTCHA bersama URL halaman, lalu kirim token kembali ke field bernama altcha tanpa menyentuhnya sedikit pun. Di proyek bertipe, persempit tipe token sekali setelah pemecahan, karena satu tipe hasil mencakup semua tipe CAPTCHA dan field ALTCHA bersifat opsional di dalamnya. Jaga agar fetch, pemecahan, dan pengiriman berada dalam blok yang sama, sebab jendela challenge bisa tertutup dalam dua menit dan challenge yang kedaluwarsa terlihat persis seperti jawaban salah. Alihkan ke Server mode begitu proses Node tidak lagi berbagi mesin dengan solver.
- Apa itu challenge dan bagaimana tipe ini bekerja: halaman pemecah ALTCHA.
- Semua metode lain yang disediakan paket Node: halaman solver Node.js.
Satu hal terakhir yang mengubah cara Anda merancang percobaan ulang. Karena pemecah captcha tanpa batas menghitung proof of work di mesin yang sudah Anda miliki, mencoba ulang challenge yang kedaluwarsa hanya memakan beberapa milidetik CPU Anda sendiri dan tidak lebih, jadi Anda leluasa mengambil challenge baru daripada merawat challenge yang sudah basi.
