Cara Memecahkan reCAPTCHA di Bun dengan File .env dan bun test

Untuk memecahkan reCAPTCHA di Bun, tambahkan SDK Node.js CapSkip dengan bun add. Bangun klien dari nilai yang sudah dimuat Bun dari file .env Anda, lalu panggil pemecah CAPTCHA dan kirim token. CapSkip berjalan di PC Windows Anda sendiri, sehingga tidak ada tagihan per pemecahan. Kodenya pendek, dan SDK berfungsi di Bun apa adanya. Tiga kebiasaan Bun tidak seperti yang diharapkan developer Node. Bun membaca file .env tanpa diminta, dan hanya dari folder tempat Anda menjalankannya. Timeout pada socket yang menggantung melakukan reject tetapi tidak mengakhiri proses. Dan bun test menghentikan setiap test setelah lima detik, yang lebih pendek daripada pemecahan sungguhan. Panduan ini membahas masing-masingnya.
Apa yang Anda butuhkan
- CapSkip yang berjalan di PC Windows, dengan pemecah reCAPTCHA diaktifkan. Lihat panduan penyiapan yang menjelaskan cara menjalankan CapSkip untuk pertama kali.
- Bun untuk Windows. Semua yang ada di bawah dijalankan di Windows 11 dengan Bun 1.4.3 dan capskip 1.3.0 dari npm. Versi Bun yang lebih lama tidak diuji.
- Prompt PowerShell. Perintahnya memakai sintaks PowerShell.
- Halaman dengan kotak centang reCAPTCHA v2 di dalam sebuah form. Contohnya login ke form bergaya Django yang membawa token CSRF. Ubah nama field agar sesuai dengan situs Anda.
- Sebuah alamat untuk pemecah CAPTCHA. Dalam mode Local, CapSkip menjawab di 127.0.0.1 hanya untuk perangkat itu. Dalam mode Server, CapSkip mendengarkan di alamat jaringan atau IP publik Anda, sehingga skrip di mesin lain dapat memanggilnya melalui API. Keduanya diatur di pengaturan koneksi.
Pengukuran di bawah berasal dari mock CapSkip (pengganti CapSkip yang dibuat khusus untuk pengujian) dan halaman login mock di PC yang sama. Bun dan SDK-nya adalah yang asli. Bun dan SDK seharusnya berperilaku sama di situs Anda sendiri, tetapi hanya mock yang dipakai. Waktu pemecahan serta aturan menerima dan menolak pada halaman login adalah milik mock itu sendiri.
Langkah 1: instal Bun
Halaman instalasi resmi Bun memberikan perintah di bawah, dan juga mencantumkan npm install -g bun. Pengujian untuk panduan ini memakai Bun 1.4.3 yang diinstal lewat npm ke folder proyek. Perintah penginstalnya sendiri tidak dijalankan di sini. Buka jendela PowerShell baru setelahnya, agar PATH diperbarui.
# PowerShell: Bun's official installer (or: npm install -g bun) powershell -c "irm bun.sh/install.ps1|iex" # Open a new PowerShell window so PATH is refreshed, then check: bun --version
Langkah 2: buat proyek lebih dulu
Jalankan bun init sebelum bun add. Jika folder tidak memiliki package.json, bun add mencari ke atas untuk yang terdekat dan mengubah file itu sebagai gantinya. Pada folder uji di dalam folder induk pengecoh, ia mempertahankan paket lain milik folder induk dan menambahkan capskip ke folder induk itu. Folder induk Anda sendiri bisa berupa folder profil atau root monorepo. Membuat file proyek lebih dulu menjaga instalasi tetap di tempat yang Anda harapkan.
# Make the folder, then create package.json BEFORE bun add. mkdir captcha-bun cd captcha-bun bun init -y bun add capskip
Perintah init menulis tsconfig.json yang ketat, index.ts, README.md, .gitignore, dan package.json dengan tipe modul yang sudah diatur. Perintah itu juga menginstal TypeScript dan definisi tipe Bun. Perintah add menyimpan capskip sebagai versi 1.3.0 atau lebih baru dan menulis lockfile teks bernama bun.lock. SDK-nya berformat CommonJS. Named import yang dipakai di bawah maupun require sama-sama berfungsi, berbeda dengan Deno, tempat require gagal.
Tidak menjalankan init punya biaya kedua. Bun me-resolve import dengan menelusuri folder ke atas. Tanpa node_modules di proyek Anda, Bun memuat capskip 1.0.2 yang lebih lama dari node_modules di folder induk pada PC uji. Tidak ada error yang menyebutkannya.
Langkah 3: taruh pengaturan di file .env
Buat file bernama .env di folder proyek. Bun memuatnya sebelum kode Anda berjalan, sehingga nilainya ada di Bun.env dan process.env, dan keduanya selalu sama. SDK tidak membaca variabel lingkungan apa pun sendiri. Skrip Anda membaca ketiga nilai itu dan meneruskannya, yang merupakan langkah berikutnya. Baris LOGIN_URL dipakai untuk test live di bagian bawah.
# .env in the folder you run bun from (bun init already git-ignores it) CAPSKIP_HOST=127.0.0.1 CAPSKIP_PORT=8080 CAPSKIP_API_KEY=capskip LOGIN_URL=https://example.com/login
Pahami aturannya, karena file .env yang nyasar bisa mengejutkan Anda:
- Bun membaca file .env hanya dari folder tempat Anda menjalankannya. Menjalankan skrip lewat path dari folder lain tidak membaca apa pun, dan .env satu folder di atasnya juga tidak dibaca.
- Variabel yang disetel di shell menang. Setelah itu .env.local, lalu file untuk mode, yaitu .env.development di bawah bun run kecuali NODE_ENV disetel, lalu .env biasa.
- File .gitignore dari bun init mencakup .env dan .env.local. File itu tidak mencakup .env.production, .env.test, atau .env.development, jadi tambahkan sebelum Anda commit.
- Tanda kutip, komentar di akhir baris, dan ekspansi ${OTHER} berfungsi di dalam file.
Langkah 4: bangun klien CapSkip
Simpan blok-blok berikutnya satu per satu dalam file bernama solve.ts. Langkah 4 sampai 9 semuanya masuk ke satu file itu, dan seluruh file ditampilkan mendekati akhir. Nilai default membuat skrip berjalan tanpa penyiapan. CAPSKIP_API_KEY hanya berpengaruh jika Anda mengaktifkan validasi API key di CapSkip.
import {
ApiException,
CapSkip,
NetworkException,
TimeoutException,
} from "capskip";
// Bun has already loaded .env into Bun.env. The SDK reads no
// environment variable itself, so pass the three values in.
const solver = new CapSkip({
host: Bun.env.CAPSKIP_HOST ?? "127.0.0.1",
port: Number(Bun.env.CAPSKIP_PORT ?? 8080),
apiKey: Bun.env.CAPSKIP_API_KEY ?? "capskip",
});Langkah 5: kelola cookie sendiri
Form login membutuhkan cookie dari halaman yang menampilkannya, dan fetch di Bun tidak menyimpan cookie. Baca setiap baris Set-Cookie dengan getSetCookie. Pemanggilan headers.get yang biasa menggabungkan baris-baris itu dengan koma, dan koma di dalam tanggal Expires lalu merusak pemisahan.
// fetch keeps no cookies, so collect every Set-Cookie line.
// (headers.get("set-cookie") would glue them into one string.)
const jar = new Map<string, string>();
function keepCookies(response: Response): void {
for (const line of response.headers.getSetCookie()) {
const pair = line.split(";")[0] ?? "";
const eq = pair.indexOf("=");
jar.set(pair.slice(0, eq).trim(), pair.slice(eq + 1).trim());
}
}
const cookies = () =>
Array.from(jar, ([name, value]) => `${name}=${value}`).join("; ");Langkah 6: baca sitekey dan token CSRF
Bagian ini milik Anda, bukan milik CapSkip. Bagian ini membuka halaman login dan mengambil kedua nilai itu. Sesuaikan pola dengan markup Anda.
// Load the login page, keep its cookies, and read the sitekey and
// the CSRF token. Adjust the two patterns to your page's markup.
async function loadForm(pageUrl: string) {
const page = await fetch(pageUrl);
keepCookies(page);
const html = await page.text();
const sitekey = html.match(/data-sitekey="([^"]+)"/)?.[1];
const csrf = html.match(/name="csrfmiddlewaretoken" value="([^"]+)"/)?.[1];
if (!sitekey || !csrf) throw new Error("sitekey or CSRF token not found");
return { sitekey, csrf };
}Langkah 7: tambahkan timeout yang berfungsi
SDK tidak menetapkan timeout socket. CapSkip yang menerima pemanggilan dan tidak pernah menjawab membuat skrip menggantung. Dalam pengujian, Bun tidak mencetak apa pun dan tidak pernah keluar, dan masih menunggu setelah 400 detik. Opsi timeout milik SDK hanya diperiksa di antara polling, jadi tidak bisa membantu saat satu request sedang macet. Blok di bawah melakukan race antara pemecahan dan sebuah timer.
// The SDK sets no socket timeout, so a CapSkip that accepts the call
// and never answers would hang the script. Race the solve against a
// timer. In Bun an AbortSignal.timeout does not keep the process alive.
function withTimeout<T>(work: Promise<T>, ms: number): Promise<T> {
const signal = AbortSignal.timeout(ms);
const limit = new Promise<never>((_, reject) => {
signal.addEventListener("abort", () =>
reject(new TimeoutException(`no answer from CapSkip in ${ms} ms`)),
);
});
return Promise.race([work, limit]);
}
// The SDK gives up after 300 s by itself, so 330 s only fires when
// CapSkip goes silent. code is typed "string or object", hence String().
async function getToken(sitekey: string, pageUrl: string): Promise<string> {
const solving = solver.recaptcha(sitekey, pageUrl);
const { code } = await withTimeout(solving, 330_000);
return String(code);
}Bun berbeda dari Deno di sini, dan menguntungkan Anda. Timer dari AbortSignal.timeout tidak membuat Bun tetap hidup, jadi pemecahan yang normal tetap keluar begitu selesai. Dalam pengujian, skrip dengan batas 330 detik di sekitar pemecahan 1.8 detik berakhir pada sekitar 1.8 detik. setTimeout biasa yang tidak pernah Anda hapus adalah soal lain. Ia membuat Bun tetap hidup sampai terpicu, 20.04 detik untuk batas 20 detik. Hapus timer seperti itu di blok finally atau pakai signal.
Masalahnya ada di arah sebaliknya. Ketika batas habis pada socket yang menggantung, promise langsung di-reject, pada 3.0 detik dalam pengujian dengan batas 3 detik. Setelah itu Bun tetap hidup, karena socket yang macet menahannya terbuka. Itulah sebabnya Langkah 9 diakhiri dengan process.exit. Hal yang sama terjadi ketika timer terpicu lebih dulu sementara CapSkip masih menjawab belum siap. SDK terus melakukan polling di latar belakang, dan proses hidup sampai timeout milik SDK sendiri, yaitu 12.8 detik dengan batas 12.
Pilih batas yang lebih kecil jika skrip Anda harus gagal lebih cepat. Pengujian menjalankan blok itu dengan 4 detik dan CapSkip yang diam pada pengiriman, lalu pada polling. Keduanya mencetak pesan dari Langkah 9 dan keluar dengan kode 1 pada sekitar 4.04 detik. Pemecahan normal keluar dengan kode 0 pada sekitar 1.8 detik.
Langkah 8: pecahkan, kirim, dan periksa hasilnya
Blok berikutnya adalah login. Token reCAPTCHA hanya berlaku sekali, jadi setiap login memecahkan miliknya sendiri. Ada dua detail yang penting. Situs yang ketat seperti Django melalui HTTPS menolak form post tanpa Referer, dan fetch tidak mengirimnya, jadi blok ini menyetelnya. Lalu fetch mengikuti 302 secara default dan membuang cookie baru di tengah jalan. Karena itu blok ini menyetel redirect ke manual dan membaca cookie dari 302. Ubah nama field, username, dan penanda Welcome back agar sesuai dengan situs Anda.
// fetch sends no Referer, and a strict site rejects the post without
// one. redirect "manual" keeps the 302 so we can read its new cookie.
export async function login(pageUrl: string): Promise<boolean> {
jar.clear();
const { sitekey, csrf } = await loadForm(pageUrl);
const token = await getToken(sitekey, pageUrl);
const response = await fetch(pageUrl, {
method: "POST",
redirect: "manual",
headers: { Cookie: cookies(), Referer: pageUrl },
body: new URLSearchParams({
csrfmiddlewaretoken: csrf,
username: "YOUR_USERNAME",
password: "YOUR_PASSWORD",
"g-recaptcha-response": token,
}),
});
keepCookies(response);
if (response.status !== 302) {
const body = await response.text();
throw new Error(`login rejected: ${response.status} ${body}`);
}
// Swap "Welcome back" for a marker your own site shows after login.
const next = new URL(response.headers.get("location") ?? "/", pageUrl);
const home = await fetch(next, { headers: { Cookie: cookies() } });
return (await home.text()).includes("Welcome back");
}Langkah 9: laporkan error dan jalankan
Port yang tertutup, error dari CapSkip, dan pemecahan yang lambat adalah tiga class exception yang berbeda, dan instanceof membedakannya di Bun. Port yang tertutup memunculkan NetworkException dalam sekitar 10 milidetik dengan pesan connect ECONNREFUSED 127.0.0.1 dan port-nya. Nama host localhost hampir secepat itu tetapi membiarkan pesan kosong, dengan kode error pada cause. Blok ini beralih ke kode tersebut.
// A closed port, a bad reply and a slow solve are three different
// exception classes. A host NAME can leave the message empty, so fall
// back to the error code on .cause.
function explain(err: unknown): string {
if (err instanceof NetworkException) {
const code = (err.cause as { code?: string } | undefined)?.code;
return `CapSkip is not reachable: ${err.message || code}`;
}
if (err instanceof ApiException) return `CapSkip replied: ${err.message}`;
if (err instanceof TimeoutException) return `Too slow: ${err.message}`;
return err instanceof Error ? (err.stack ?? err.message) : String(err);
}
// import.meta.main is true only when this file is run directly, so
// the tests can import login() without starting a login.
if (import.meta.main) {
try {
const ok = await login(Bun.argv[2] ?? "https://example.com/login");
console.log(ok ? "logged in" : "NOT logged in");
process.exit(ok ? 0 : 2);
} catch (err) {
console.error(explain(err));
// A hung socket would keep Bun alive, so leave explicitly.
process.exit(1);
}
}Ganti URL contoh dengan halaman login Anda sendiri, dan jalankan dari folder proyek. Bun mengambil file .env di sana. Variabel yang disetel di PowerShell mengalahkan nama yang sama di file, dan tetap tersetel selama sisa sesi PowerShell itu, jadi blok ini menghapusnya lagi. Dua perintah terakhir mematikan pemuatan otomatis, atau membaca file pilihan Anda sebagai gantinya. Buat file itu lebih dulu. File yang tidak ada dilewati tanpa pesan, dan .env juga tidak dibaca.
# Run it. Bun reads .env from the folder you run this in. bun run solve.ts https://example.com/login # A variable set in PowerShell beats the same name in .env. $env:CAPSKIP_API_KEY = "my-key" bun run solve.ts https://example.com/login Remove-Item Env:CAPSKIP_API_KEY # Ignore .env completely, or read a different file instead. The second # file must exist: a missing one is skipped and .env is not read either. bun run --no-env-file solve.ts https://example.com/login bun run --env-file=.env.office solve.ts https://example.com/login
Run yang baik mencetak logged in dan keluar dengan kode 0. Terhadap mock, pemecahan yang ditahan mock selama satu detik memakan 1.77 detik. SDK melakukan polling segera, lalu menunggu 0.25, 0.5, 1, 2, dan 4 detik, dan 5 detik setelah itu. Pemecahan yang singkat tidak dibulatkan menjadi lima detik tetap.
Uji dengan bun test
Bun menyertakan test runner sendiri. Test runner itu menjalankan file yang bernama seperti solve.test.ts, dan batas default-nya 5000 milidetik per test. Pemecahan reCAPTCHA sungguhan bisa lebih lama. Test yang menunggu mock 8 detik gagal dengan pesan ini, dan run keluar dengan kode 1:
- (fail) logs in through CapSkip [5001.15ms]
- this test timed out after 5000ms.
Naikkan batasnya dengan salah satu dari tiga cara: argumen ketiga test, setDefaultTimeout seperti di bawah, atau opsi timeout dari bun test. Ketiganya lulus terhadap mock 8 detik. Kunci timeout di bawah bagian test pada bunfig.toml tidak berfungsi. Test tetap gagal pada 5000 milidetik, dan dokumentasi bunfig milik Bun tidak mencantumkan kunci seperti itu. Test live membaca LOGIN_URL dari file .env, jadi setel ke halaman login Anda. Simpan yang ini sebagai solve.test.ts.
import { expect, setDefaultTimeout, test } from "bun:test";
import { login } from "./solve";
// bun test stops every test after 5 s, and a real solve can take
// longer. Raise the limit for the whole file (or pass a number as the
// third argument of test(), or run: bun test --timeout 120000).
setDefaultTimeout(120_000);
test("logs in through CapSkip", async () => {
const ok = await login(Bun.env.LOGIN_URL ?? "https://example.com/login");
expect(ok).toBe(true);
});Sebagian besar suite Anda tidak membutuhkan pemecah CAPTCHA sama sekali. Test kedua mengganti SDK dengan tiruan dan berjalan terhadap situs pengganti kecil, sehingga lulus dengan CapSkip ditutup. Dua hal mudah keliru. Import solve.ts harus dinamis, karena import statis berjalan sebelum mock.module. Dan tiruannya bocor. Mock tetap berlaku untuk file test lainnya. bun test biasa membuat solve.test.ts gagal dengan login rejected: 403, karena kini ia memakai pemecah tiruan. Simpan yang ini sebagai fake.test.ts.
import { expect, mock, test } from "bun:test";
// Replace the SDK before solve.ts loads it. The import must be dynamic,
// because a static import runs before mock.module() does.
mock.module("capskip", () => ({
CapSkip: class {
async recaptcha() {
return { captchaId: "1", code: "fake-token" };
}
},
ApiException: class extends Error {},
NetworkException: class extends Error {},
TimeoutException: class extends Error {},
}));
const { login } = await import("./solve");
// A tiny stand-in login site: it only accepts the fake token.
const site = Bun.serve({
port: 0,
async fetch(req) {
const url = new URL(req.url);
if (req.method === "GET" && url.pathname === "/login") {
return new Response(
'<div data-sitekey="K"></div>' +
'<input name="csrfmiddlewaretoken" value="C">',
);
}
if (req.method === "POST") {
const form = await req.formData();
if (form.get("g-recaptcha-response") !== "fake-token")
return new Response("bad token", { status: 403 });
return new Response(null, {
status: 302,
headers: { Location: "/home" },
});
}
return new Response("Welcome back");
},
});
test("login posts the token CapSkip returned", async () => {
expect(await login(`http://127.0.0.1:${site.port}/login`)).toBe(true);
await site.stop();
});Perbaikannya adalah opsi isolate, yang memberi setiap file objek global yang baru. Dengannya, kedua file lulus. Bun juga menyetel NODE_ENV ke test selama bun test, sehingga memuat .env.test dan melewati .env.local.
# Run every *.test.ts file. --isolate gives each file a fresh global # object, so the fake SDK in fake.test.ts cannot leak into the others. bun test --isolate # The default limit is 5 s per test. Raise it for all tests (ms). bun test --isolate --timeout 120000
Buat satu file .exe
Perintah compile menaruh runtime Bun, skrip Anda, dan SDK dalam satu file. Prosesnya memakan 0.3 detik di sini dan menghasilkan file 96.4 MB, seukuran Bun itu sendiri. Exe berjalan dengan PATH yang tidak memuat Bun. File .env tidak ditanamkan ke dalam exe. Exe membaca .env dari folder tempat ia dijalankan, saat run time. Ia tidak memakai .env dari folder build atau yang berada di samping exe. Jika dimulai dari folder tanpa .env, port kembali undefined, dan nilai default di Langkah 4 berlaku. Di Task Scheduler, setel folder Start in ke folder yang memuat .env Anda. Tambahkan opsi no-compile-autoload-dotenv ke perintah build dan exe mengabaikan file itu. Variabel lingkungan yang sebenarnya tetap berfungsi.
# One .exe with the Bun runtime and the SDK inside. Run it from the # folder that holds your .env (Task Scheduler: set "Start in"). bun build --compile --outfile solve.exe solve.ts .\solve.exe https://example.com/login
Periksa tipe sesuai kebutuhan
Perintah bun run membuang tipe dan tidak pernah memeriksanya. File dengan error tipe tetap berjalan dan berhasil login. Bun 1.4.3 memiliki perintah bun check yang menjalankan TypeScript pada seluruh proyek, dan kedua file test bersih. Ada tiga detail penulisan tipe dari SDK yang perlu diketahui. Field code dideklarasikan sebagai string atau objek, itulah sebabnya Langkah 7 membungkusnya dengan String. Field token bersifat opsional. Dan tipe opsi pemanggilan recaptcha menerima nama apa pun, sehingga opsi yang salah eja tetap lolos kompilasi. Opsi constructor yang salah eja seperti hots adalah error.
# bun run never type checks. Check on demand (TypeScript from bun init): bun check
Menjalankan skrip terhadap CapSkip di mesin lain
Skrip dan pemecah CAPTCHA tidak harus berada di PC yang sama. Alihkan CapSkip ke mode Server di pengaturan koneksi, dan ia akan mendengarkan di alamat jaringan atau IP publik Anda. Taruh alamat itu di .env pada mesin yang menjalankan skrip. IP publik statis direkomendasikan untuk akses jarak jauh yang stabil. Pemecah CAPTCHA tetap berupa mesin Windows milik Anda sendiri, dan pemecahannya tetap tanpa kuota.
# .env on the PC that runs the script, with CapSkip in Server mode # on another PC. Use that PC's address, and an API key if you set one. CAPSKIP_HOST=192.168.1.50 CAPSKIP_PORT=8080 CAPSKIP_API_KEY=your-long-random-key
Perhatikan dua hal. SDK berbicara dengan CapSkip lewat HTTP biasa, sehingga API key dikirim sebagai teks polos. Jaga agar tautan itu berada di jaringan pribadi atau VPN, dan aktifkan validasi API key. Lalu CapSkip membuka port-nya untuk semua alamat saat mode Server dimulai. Untuk membatasi akses hanya pada mesin yang Anda harapkan, tambahkan aturan blokir Windows Firewall untuk alamat lainnya. Penyiapan jarak jauh itu tidak dijalankan untuk panduan ini. Hanya file .env yang di-parse.
Seluruh solve.ts
Ini adalah Langkah 4 sampai 9 dalam satu file, untuk memulai dengan cepat. Jika Anda menyimpan blok-blok itu satu per satu, Anda sudah memilikinya. Jangan tempel file itu dua kali, atau Bun melaporkan bahwa sebuah nama sudah dideklarasikan.
import {
ApiException,
CapSkip,
NetworkException,
TimeoutException,
} from "capskip";
// Bun has already loaded .env into Bun.env. The SDK reads no
// environment variable itself, so pass the three values in.
const solver = new CapSkip({
host: Bun.env.CAPSKIP_HOST ?? "127.0.0.1",
port: Number(Bun.env.CAPSKIP_PORT ?? 8080),
apiKey: Bun.env.CAPSKIP_API_KEY ?? "capskip",
});
// fetch keeps no cookies, so collect every Set-Cookie line.
// (headers.get("set-cookie") would glue them into one string.)
const jar = new Map<string, string>();
function keepCookies(response: Response): void {
for (const line of response.headers.getSetCookie()) {
const pair = line.split(";")[0] ?? "";
const eq = pair.indexOf("=");
jar.set(pair.slice(0, eq).trim(), pair.slice(eq + 1).trim());
}
}
const cookies = () =>
Array.from(jar, ([name, value]) => `${name}=${value}`).join("; ");
// Load the login page, keep its cookies, and read the sitekey and
// the CSRF token. Adjust the two patterns to your page's markup.
async function loadForm(pageUrl: string) {
const page = await fetch(pageUrl);
keepCookies(page);
const html = await page.text();
const sitekey = html.match(/data-sitekey="([^"]+)"/)?.[1];
const csrf = html.match(/name="csrfmiddlewaretoken" value="([^"]+)"/)?.[1];
if (!sitekey || !csrf) throw new Error("sitekey or CSRF token not found");
return { sitekey, csrf };
}
// The SDK sets no socket timeout, so a CapSkip that accepts the call
// and never answers would hang the script. Race the solve against a
// timer. In Bun an AbortSignal.timeout does not keep the process alive.
function withTimeout<T>(work: Promise<T>, ms: number): Promise<T> {
const signal = AbortSignal.timeout(ms);
const limit = new Promise<never>((_, reject) => {
signal.addEventListener("abort", () =>
reject(new TimeoutException(`no answer from CapSkip in ${ms} ms`)),
);
});
return Promise.race([work, limit]);
}
// The SDK gives up after 300 s by itself, so 330 s only fires when
// CapSkip goes silent. code is typed "string or object", hence String().
async function getToken(sitekey: string, pageUrl: string): Promise<string> {
const solving = solver.recaptcha(sitekey, pageUrl);
const { code } = await withTimeout(solving, 330_000);
return String(code);
}
// fetch sends no Referer, and a strict site rejects the post without
// one. redirect "manual" keeps the 302 so we can read its new cookie.
export async function login(pageUrl: string): Promise<boolean> {
jar.clear();
const { sitekey, csrf } = await loadForm(pageUrl);
const token = await getToken(sitekey, pageUrl);
const response = await fetch(pageUrl, {
method: "POST",
redirect: "manual",
headers: { Cookie: cookies(), Referer: pageUrl },
body: new URLSearchParams({
csrfmiddlewaretoken: csrf,
username: "YOUR_USERNAME",
password: "YOUR_PASSWORD",
"g-recaptcha-response": token,
}),
});
keepCookies(response);
if (response.status !== 302) {
const body = await response.text();
throw new Error(`login rejected: ${response.status} ${body}`);
}
// Swap "Welcome back" for a marker your own site shows after login.
const next = new URL(response.headers.get("location") ?? "/", pageUrl);
const home = await fetch(next, { headers: { Cookie: cookies() } });
return (await home.text()).includes("Welcome back");
}
// A closed port, a bad reply and a slow solve are three different
// exception classes. A host NAME can leave the message empty, so fall
// back to the error code on .cause.
function explain(err: unknown): string {
if (err instanceof NetworkException) {
const code = (err.cause as { code?: string } | undefined)?.code;
return `CapSkip is not reachable: ${err.message || code}`;
}
if (err instanceof ApiException) return `CapSkip replied: ${err.message}`;
if (err instanceof TimeoutException) return `Too slow: ${err.message}`;
return err instanceof Error ? (err.stack ?? err.message) : String(err);
}
// import.meta.main is true only when this file is run directly, so
// the tests can import login() without starting a login.
if (import.meta.main) {
try {
const ok = await login(Bun.argv[2] ?? "https://example.com/login");
console.log(ok ? "logged in" : "NOT logged in");
process.exit(ok ? 0 : 2);
} catch (err) {
console.error(explain(err));
// A hung socket would keep Bun alive, so leave explicitly.
process.exit(1);
}
}Kesalahan umum saat memecahkan reCAPTCHA di Bun
| Apa yang Anda lihat | Penyebab | Perbaiki |
|---|---|---|
| this test timed out after 5000ms | bun test memotong setiap test pada lima detik | setDefaultTimeout, atau argumen ketiga untuk test |
| login rejected: 403 hanya di solve.test.ts | mock.module di file lain masih berlaku | Jalankan bun test dengan opsi isolate |
| NetworkException dengan pesan kosong | Host berupa nama seperti localhost, jadi error berada pada cause | Cetak err.cause.code, atau pakai 127.0.0.1 |
| NetworkException: connect ECONNREFUSED 127.0.0.1 | CapSkip tidak berjalan, atau port-nya salah | Jalankan CapSkip dan periksa port di pengaturan koneksi |
| Object is not a constructor | Default import memberikan seluruh objek exports | Impor CapSkip berdasarkan nama, dalam kurung kurawal |
| Skrip menggantung dan tidak mencetak apa pun | CapSkip menerima pemanggilan lalu diam | Gunakan race withTimeout dan akhiri dengan process.exit |
| Versi capskip yang salah termuat | Tidak ada node_modules di proyek, jadi Bun menemukan salinan lama di folder yang lebih tinggi | Jalankan bun init dan bun add di dalam folder proyek |
| Pengaturan kembali undefined di exe | Exe membaca .env dari folder kerjanya | Jalankan dari folder itu, atau setel Start in |
| fetch ke 127.0.0.1 gagal dengan ECONNREFUSED | HTTP_PROXY menunjuk ke proxy yang mati, dan fetch memakainya | Setel NO_PROXY ke 127.0.0.1,localhost |
| ApiException: ERROR_CAPTCHA_UNSOLVABLE | CapSkip tidak bisa memecahkan yang satu ini | Coba ulang pemecahan |
FAQ
Apakah SDK Node.js CapSkip berfungsi di Bun?
Ya, di Bun 1.4.3 dengan capskip 1.3.0. Import, pemecahan penuh, keempat class exception, dan polling semuanya berperilaku seperti di Node. Pemecahan memakan sekitar 1.8 detik baik di Bun 1.4.3 maupun Node 25 terhadap mock yang sama. Untuk versi Node biasa dari penyiapan ini, lihat panduan memecahkan reCAPTCHA v2 di Node.js.
Apa bedanya dengan Deno?
Deno memblokir akses jaringan dan file sampai Anda mengizinkannya, dan timer AbortSignal membuat Deno tetap hidup. Bun tidak memiliki flag izin, jadi tidak ada yang perlu diizinkan. Jebakannya adalah pemuatan .env otomatis, socket menggantung yang bertahan setelah timeout, dan batas test lima detik. Lihat panduan Deno yang membahas sisi izin.
Bisakah saya menjalankan beberapa pemecahan sekaligus?
Ya, dengan Promise.all pada beberapa pemanggilan pemecah CAPTCHA. Di SDK Node, AsyncCapSkip hanyalah nama lain untuk CapSkip, jadi tidak menambah apa pun dibanding klien biasa. Perhatikan batas milik CapSkip sendiri: pengaturan reCAPTCHA Max. Threads default-nya 10, dan job tambahan menunggu thread yang kosong. Konkurensi tidak diukur di Bun untuk panduan ini.
Bisakah skrip di platform hosted memakai CapSkip saya?
Platform hosted berjalan di mesin milik pihak lain, jadi tidak bisa menjangkau 127.0.0.1 di PC Anda. Jalankan CapSkip dalam mode Server di mesin Windows yang Anda kendalikan, dengan IP publik statis. Taruh alamat itu di .env atau di secret store milik platform. CapSkip sudah mengizinkan semua alamat di port-nya, jadi tambahkan aturan blokir untuk sisanya, dan ingat bahwa key dikirim sebagai teks polos lewat HTTP biasa. Tidak ada platform hosted yang diuji untuk panduan ini.
Versi singkatnya
Untuk memecahkan reCAPTCHA di Bun, jalankan bun init sebelum bun add. Simpan pengaturan CapSkip di file .env dan teruskan ke klien sendiri. Lakukan race antara setiap pemecahan dan sebuah timer, dan akhiri skrip dengan process.exit agar socket yang menggantung tidak bisa menjaganya tetap hidup. Naikkan batas test lima detik, dan sediakan test dengan pemecah tiruan di samping test live.
- Setiap tipe CAPTCHA yang ditangani paket Node.js: halaman pemecah CAPTCHA Node.js.
- Cara kerja pemecah kotak centang: halaman pemecah reCAPTCHA v2.
- Tugas yang sama di runtime lain: memecahkan reCAPTCHA di Deno.
Skrip yang memecahkan CAPTCHA beberapa ratus kali per malam jumlahnya cepat menumpuk. Jika dijalankan melalui sebuah pemecah captcha tanpa batas lokal di mesin Windows Anda sendiri, pemecahan itu tidak pernah berubah menjadi tagihan.
