Bun’da .env Dosyaları ve bun test ile reCAPTCHA Nasıl Çözülür

Bun’da reCAPTCHA çözmek için CapSkip Node.js SDK’sını bun add ile ekleyin. İstemciyi, Bun’un .env dosyanızdan zaten yüklediği değerlerle oluşturun, ardından çözücüyü çağırın ve token’ı gönderin. CapSkip kendi Windows bilgisayarınızda çalışır, dolayısıyla çözüm başına fatura yoktur. Kod kısadır ve SDK Bun altında olduğu gibi çalışır. Üç Bun alışkanlığı, bir Node geliştiricisinin beklediği gibi değildir. Bun .env dosyalarını istenmeden okur ve yalnızca çalıştırdığınız klasörden okur. Takılı kalan bir socket üzerindeki zaman aşımı promise’i reddeder ama süreci bitirmez. Ve bun test her testi beş saniye sonra durdurur; bu, gerçek bir çözümden daha kısadır. Bu rehber bunların her birini ele alır.
Neye ihtiyacınız var
- reCAPTCHA çözücüsü açık hâlde bir Windows bilgisayarında çalışan CapSkip. İlk başlatmayı kurulum kılavuzu adım adım anlatıyor.
- Windows için Bun. Aşağıdaki her şey Windows 11 üzerinde, Bun 1.4.3 ve npm’den gelen capskip 1.3.0 ile çalıştırıldı. Daha eski Bun sürümleri test edilmedi.
- Bir PowerShell istemi. Komutlar PowerShell sözdizimini kullanır.
- Bir formda reCAPTCHA v2 onay kutusu bulunan bir sayfa. Örnek, bir CSRF token’ı taşıyan Django tarzı bir forma giriş yapar. Alan adlarını kendi sitenize uyacak şekilde değiştirin.
- Çözücü için bir adres. Local modunda CapSkip 127.0.0.1 üzerinden yalnızca o cihaza yanıt verir. Server modunda ise ağ adresinizi veya genel IP’nizi dinler, böylece başka bir makinedeki bir betik onu API üzerinden çağırabilir. İkisi de şurada ayarlanır: bağlantı ayarları.
Aşağıdaki ölçümler, aynı bilgisayardaki sahte (mock) bir CapSkip ve sahte bir giriş sayfasından alındı. Bun ve SDK gerçek olanlardır. Bun ve SDK kendi sitenize karşı da aynı davranmalıdır, ancak yalnızca bir mock kullanıldı. Çözüm süreleri ile giriş sayfasının kabul ve ret kuralları mock’un kendisine aittir.
1. Adım: Bun’u kurun
Bun’un kendi kurulum sayfası aşağıdaki komutu verir ve ayrıca npm install -g bun komutunu da listeler. Bu rehberin testlerinde, bir proje klasörüne npm ile kurulan Bun 1.4.3 kullanıldı. Yükleyici komutunun kendisi burada çalıştırılmadı. PATH değişkeninin yenilenmesi için sonrasında yeni bir PowerShell penceresi açın.
# 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
2. Adım: önce projeyi oluşturun
bun add’den önce bun init çalıştırın. Klasörde package.json yoksa bun add yukarı doğru en yakın olanı arar ve onun yerine o dosyayı değiştirir. Tuzak olarak hazırlanmış (decoy) bir üst klasörün içindeki bir test klasöründe, üst klasörün diğer paketlerini korudu ve capskip’i üst klasöre ekledi. Kendi üst klasörünüz bir profil klasörü ya da bir monorepo kökü olabilir. Proje dosyasını önce oluşturmak kurulumu beklediğiniz yerde tutar.
# Make the folder, then create package.json BEFORE bun add. mkdir captcha-bun cd captcha-bun bun init -y bun add capskip
init komutu katı bir tsconfig.json, bir index.ts, bir README.md, bir .gitignore ve modül türü ayarlanmış bir package.json yazar. Ayrıca TypeScript’i ve Bun tür tanımlarını kurar. add komutu capskip’i 1.3.0 veya üzeri sürüm olarak kaydeder ve bun.lock adlı bir metin lockfile yazar. SDK CommonJS’tir. Aşağıda kullanılan adlandırılmış import de require de çalışır; bu, require’ın başarısız olduğu Deno’dan farklıdır.
bun init’i atlamanın ikinci bir maliyeti vardır. Bun bir import’u klasörlerde yukarı yürüyerek çözümler. Projenizde node_modules yokken, test bilgisayarında üst klasördeki bir node_modules’tan daha eski bir capskip 1.0.2 yükledi. Hiçbir hata bunu söylemedi.
3. Adım: ayarları bir .env dosyasına koyun
Proje klasöründe .env adlı bir dosya oluşturun. Bun onu kodunuz çalışmadan önce yükler, bu yüzden değerler Bun.env ve process.env içindedir ve ikisi her zaman aynıdır. SDK hiçbir ortam değişkenini kendisi okumaz. Script’iniz üç değeri okur ve içeri verir; bu bir sonraki adımdır. LOGIN_URL satırı, aşağıdaki canlı test içindir.
# .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
Kuralları bilin, çünkü başıboş bir .env dosyası sizi şaşırtabilir:
- Bun .env dosyalarını yalnızca çalıştırdığınız klasörden okur. Bir script’i başka bir klasörden yoluyla çalıştırmak hiçbir şey okumaz ve bir klasör yukarıdaki .env de okunmaz.
- Kabukta ayarlanan bir değişken kazanır. Sonra .env.local gelir, sonra mod için olan dosya gelir; bu, NODE_ENV ayarlanmadıkça bun run altında .env.development dosyasıdır. En sonda düz .env gelir.
- bun init’in .gitignore dosyası .env ve .env.local dosyalarını kapsar. .env.production, .env.test ya da .env.development dosyalarını kapsamaz, bu yüzden commit’ten önce onları ekleyin.
- Dosyada tırnaklar, satır sonu yorumu ve ${OTHER} genişletmesi çalışır.
4. Adım: CapSkip istemcisini oluşturun
Sonraki blokları solve.ts adlı bir dosyaya art arda kaydedin. 4 ile 9. adımların hepsi bu tek dosyaya girer ve dosyanın tamamı sona doğru gösterilir. Varsayılan değerler sayesinde script hiçbir kurulum yapmadan çalışır. CAPSKIP_API_KEY yalnızca CapSkip’te API key validation’ı açtıysanız önemlidir.
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",
});5. Adım: çerezleri kendiniz tutun
Giriş formu, onu gösteren sayfadan gelen çerezi gerektirir ve Bun’daki fetch hiçbir çerez tutmaz. Her Set-Cookie satırını getSetCookie ile okuyun. Olağan headers.get çağrısı satırları virgülle birleştirir ve Expires tarihinin içindeki virgül bölmeyi bozar.
// 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("; ");6. Adım: sitekey’i ve CSRF token’ını okuyun
Bu kısım sizindir, CapSkip’in değil. Giriş sayfasını açar ve iki değeri çekip çıkarır. Desenleri kendi HTML yapınıza göre ayarlayın.
// 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 };
}7. Adım: işe yarayan bir zaman aşımı ekleyin
SDK hiçbir socket zaman aşımı koymaz. Bir çağrıyı kabul edip hiç yanıt vermeyen bir CapSkip script’i takılı bırakır. Testte Bun hiçbir şey yazdırmadı ve hiç çıkmadı; 400 saniye sonra hâlâ bekliyordu. SDK’nın timeout seçeneği yalnızca yoklamalar arasında kontrol edilir, bu yüzden tek bir istek takılıyken yardımcı olamaz. Aşağıdaki blok çözümü bir zamanlayıcıyla yarıştırır.
// 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 burada Deno’dan farklıdır, sizin lehinize. AbortSignal.timeout ile oluşturulan bir zamanlayıcı Bun’u ayakta tutmaz, bu yüzden normal bir çözüm biter bitmez çıkar. Testte, 1.8 saniyelik bir çözümün etrafında 330 saniyelik süre olan bir script yaklaşık 1.8 saniyede bitti. Hiç temizlemediğiniz düz bir setTimeout ise başka bir konudur. Tetiklenene kadar Bun’u ayakta tuttu: 20 saniyelik süre için 20.04 saniye. Böyle bir zamanlayıcıyı bir finally bloğunda temizleyin ya da signal’i kullanın.
Sorun öbür yöndedir. Süre, takılı bir socket üzerinde dolduğunda promise hemen reddedilir; 3 saniyelik süreli bir testte 3.0 saniyede. Sonra Bun ayakta kalır, çünkü takılı socket onu açık tutar. 9. adımın process.exit ile bitmesinin nedeni budur. CapSkip hâlâ hazır değil yanıtı veriyorken zamanlayıcı yarışı kazandığında da aynısı olur. SDK arka planda yoklamaya devam eder ve süreç, SDK’nın kendi zaman aşımına kadar yaşar; bu, 12 sınırıyla 12.8 saniye oldu.
Script’in daha hızlı başarısız olmasını istiyorsanız daha küçük bir süre seçin. Testler bloğu 4 saniyeyle, önce gönderimde sonra yoklamada sessizleşen bir CapSkip ile çalıştırdı. İkisi de 9. adımdaki mesajı yazdırdı ve yaklaşık 4.04 saniyede 1 koduyla çıktı. Normal bir çözüm yaklaşık 1.8 saniyede 0 koduyla çıktı.
8. Adım: çözün, gönderin ve sonucu kontrol edin
Sonraki blok girişin kendisidir. Bir reCAPTCHA token’ı bir kez işe yarar, bu yüzden her giriş kendi çözümünü yapar. İki ayrıntı önemlidir. HTTPS üzerinden çalışan Django gibi katı bir site Referer olmayan bir form gönderimini reddeder ve fetch hiçbir Referer göndermez, bu yüzden blok onu ayarlar. Ayrıca fetch varsayılan olarak 302 yönlendirmesini izler ve yeni çerezi yolda kaybeder. Bu yüzden blok redirect değerini manual yapar ve çerezi 302 yanıtından okur. Alan adlarını, kullanıcı adını ve Welcome back işaretini sitenize uyacak şekilde değiştirin.
// 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");
}9. Adım: hataları bildirin ve çalıştırın
Kapalı bir port, CapSkip’ten gelen bir hata ve yavaş bir çözüm üç farklı istisna sınıfıdır ve instanceof bunları Bun altında birbirinden ayırır. Kapalı bir port, yaklaşık 10 milisaniyede, mesajı connect ECONNREFUSED 127.0.0.1 ve portu içeren bir NetworkException fırlattı. localhost host adı neredeyse bu kadar hızlıydı ama mesajı boş bıraktı; hata kodu cause üzerindeydi. Blok bu koda geri döner.
// 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);
}
}Örnek URL’yi kendi giriş sayfanızla değiştirin ve proje klasöründen çalıştırın. Bun oradaki .env dosyasını alır. PowerShell’de ayarlanan bir değişken, dosyadaki aynı ada üstün gelir ve o PowerShell oturumunun geri kalanı boyunca ayarlı kalır, bu yüzden blok onu yeniden temizler. Son iki komut otomatik yüklemeyi kapatır ya da seçtiğiniz bir dosyayı okur. O dosyayı önce oluşturun. Eksik bir dosya mesajsız atlanır ve .env de okunmaz.
# 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
İyi bir çalıştırma logged in yazdırır ve 0 koduyla çıkar. Mock’a karşı, mock’un bir saniye tuttuğu bir çözüm 1.77 saniye sürdü. SDK hemen yoklar, sonra 0.25, 0.5, 1, 2 ve 4 saniye bekler, bundan sonra 5 saniye. Kısa bir çözüm düz bir beşe yuvarlanmaz.
bun test ile test edin
Bun kendi test çalıştırıcısıyla gelir. solve.test.ts gibi adlandırılmış dosyaları çalıştırır ve varsayılan sınırı test başına 5000 milisaniyedir. Gerçek bir reCAPTCHA çözümü bundan uzun sürebilir. 8 saniyelik mock’u bekleyen bir test şununla başarısız oldu ve çalıştırma 1 koduyla çıktı:
- (fail) logs in through CapSkip [5001.15ms]
- this test timed out after 5000ms.
Sınırı üç yoldan biriyle yükseltin: test’in üçüncü argümanı, aşağıdaki gibi setDefaultTimeout ya da bun test’in timeout bayrağı. Üçü de 8 saniyelik mock’a karşı geçti. bunfig.toml içindeki test başlığı altındaki bir timeout anahtarı çalışmadı. Test yine 5000 milisaniyede başarısız oldu ve Bun’un bunfig belgeleri böyle bir anahtar listelemez. Canlı test LOGIN_URL değerini .env dosyasından okur, bu yüzden onu giriş sayfanıza ayarlayın. Bunu solve.test.ts olarak kaydedin.
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);
});Test paketinizin çoğunun hiç çözücüye ihtiyacı olmamalı. İkinci test SDK’yı bir fake ile değiştirir ve küçük bir sahte siteye karşı çalışır, bu yüzden CapSkip kapalıyken geçer. İki şeyi yanlış yapmak kolaydır. solve.ts import’u dinamik olmalıdır, çünkü statik bir import mock.module’den önce çalışır. Ve fake sızar. Mock diğer test dosyaları için de geçerli kalır. Düz bun test, solve.test.ts’i login rejected: 403 ile başarısız kıldı, çünkü artık fake çözücüyü kullanıyordu. Bunu fake.test.ts olarak kaydedin.
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();
});Düzeltme, her dosyaya yeni bir global nesne veren isolate bayrağıdır. Onunla iki dosya da geçti. Bun ayrıca bun test sırasında NODE_ENV değerini test yapar, bu yüzden .env.test dosyasını yükler ve .env.local dosyasını atlar.
# 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
Tek bir .exe oluşturun
compile komutu Bun çalışma zamanını, script’inizi ve SDK’yı tek bir dosyaya koyar. Burada 0.3 saniye sürdü ve Bun’un kendi boyutu olan 96.4 MB’lık bir dosya üretti. Exe, PATH değişkeninde Bun olmayan bir ortamda çalıştı. .env dosyası içine gömülmez. Exe, çalıştırıldığı klasörün .env dosyasını çalışma anında okur. Derleme klasöründekini ya da exe’nin yanındakini kullanmaz. .env olmayan bir klasörden başlatıldığında port undefined döner ve 4. adımdaki varsayılan değerler uygulanır. Task Scheduler’da Start in klasörünü .env dosyanızın bulunduğu klasöre ayarlayın. Derleme komutuna no-compile-autoload-dotenv bayrağını eklerseniz exe dosyayı yok sayar. Gerçek değişkenler yine çalışır.
# 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
Türleri istek üzerine denetleyin
bun run komutu türleri kaldırır ve hiçbir zaman denetlemez. Tür hatası olan bir dosya yine de çalıştı ve giriş yaptı. Bun 1.4.3’te projede TypeScript çalıştıran bir bun check komutu vardır ve iki test dosyası da temizdi. SDK’nın üç tür ayrıntısını bilmekte fayda var. code alanı bir dize ya da bir nesne olarak tanımlanmıştır; 7. adımın onu String ile sarmasının nedeni budur. token alanı isteğe bağlıdır. Ve recaptcha çağrısının seçenekler türü her adı kabul eder, bu yüzden yanlış yazılmış bir seçenek derlenir. hots gibi yanlış yazılmış bir constructor seçeneği ise hatadır.
# bun run never type checks. Check on demand (TypeScript from bun init): bun check
Script’i başka bir makinedeki CapSkip’e karşı çalıştırın
Script ile çözücünün aynı bilgisayarı paylaşması gerekmez. Bağlantı ayarlarından CapSkip’i Server moduna alın; ağ adresinizi veya genel IP’nizi dinler. Bu adresi script’i çalıştıran makinenin .env dosyasına koyun. Kararlı uzaktan erişim için statik bir genel IP önerilir. Çözücü hâlâ sizin kendi Windows makinenizdir ve çözüm başına ücretlendirme hâlâ yoktur.
# .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
İki şeye dikkat edin. SDK, CapSkip ile düz HTTP üzerinden konuşur, bu yüzden API anahtarı açık metin olarak taşınır. Bu bağlantıyı özel bir ağda ya da bir VPN içinde tutun ve API key validation’ı açın. Ayrıca CapSkip, Server modu başladığında portunu her adrese açar. Erişimi beklediğiniz makinelerle sınırlamak için diğerleri için bir Windows Firewall engelleme kuralı ekleyin. Uzaktan kurulum bu rehber için çalıştırılmadı. Yalnızca .env dosyası ayrıştırıldı.
Tam solve.ts
Bu, hızlı başlangıç için 4 ile 9. adımların tek dosyada toplanmış hâlidir. Blokları tek tek kaydettiyseniz zaten sizde var. Dosyayı iki kez yapıştırmayın, yoksa Bun bir adın zaten bildirildiğini söyler.
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);
}
}Bun’da reCAPTCHA çözerken sık görülen hatalar
| Gördüğünüz | Neden | Düzeltme |
|---|---|---|
| this test timed out after 5000ms | bun test her testi beş saniyede keser | setDefaultTimeout ya da test’e üçüncü bir argüman |
| login rejected: 403 yalnızca solve.test.ts içinde | Başka bir dosyadaki mock.module hâlâ geçerli | bun test’i isolate bayrağıyla çalıştırın |
| Boş mesajlı NetworkException | Host, localhost gibi bir addır, bu yüzden hata cause üzerindedir | err.cause.code yazdırın ya da 127.0.0.1 kullanın |
| NetworkException: connect ECONNREFUSED 127.0.0.1 | CapSkip çalışmıyor ya da port yanlış | CapSkip’i başlatın ve portu bağlantı ayarlarından kontrol edin |
| Object is not a constructor | Varsayılan import, exports nesnesinin tamamını verir | CapSkip’i adıyla, süslü parantez içinde import edin |
| Script takılıyor ve hiçbir şey yazdırmıyor | CapSkip çağrıyı kabul etti ve sessizleşti | withTimeout yarışını kullanın ve process.exit ile bitirin |
| Yanlış capskip sürümü yükleniyor | Projede node_modules yok, bu yüzden Bun daha yukarıda daha eski bir kopya buldu | bun init ve bun add komutlarını proje klasörünün içinde çalıştırın |
| Ayarlar exe içinde undefined geliyor | Exe, çalışma klasörünün .env dosyasını okur | Onu o klasörden çalıştırın ya da Start in ayarlayın |
| 127.0.0.1’e fetch, ECONNREFUSED ile başarısız oluyor | HTTP_PROXY çalışmayan bir proxy’yi gösteriyor ve fetch onu kullanıyor | NO_PROXY değerini 127.0.0.1,localhost yapın |
| ApiException: ERROR_CAPTCHA_UNSOLVABLE | CapSkip bunu çözemedi | Çözümü yeniden deneyin |
FAQ
CapSkip Node.js SDK’sı Bun’da çalışır mı?
Evet, Bun 1.4.3 ve capskip 1.3.0 ile. Import, tam bir çözüm, dört istisna sınıfı ve yoklama, Node’daki gibi davrandı. Çözüm, aynı mock’a karşı hem Bun 1.4.3’te hem Node 25’te yaklaşık 1.8 saniye sürdü. Bu kurulumun düz Node sürümü için şu rehbere bakın: Node.js’te reCAPTCHA v2 çözme.
Bu, Deno’dan nasıl farklı?
Deno, siz izin verene kadar ağ ve dosya erişimini engeller ve bir AbortSignal zamanlayıcısı Deno’yu ayakta tutar. Bun’da izin bayrakları yoktur, bu yüzden izin verilecek bir şey yoktur. Tuzaklar otomatik .env yüklemesi, bir zaman aşımından sağ çıkan takılı socket ve beş saniyelik test sınırıdır. Deno rehberi izin tarafını ele alır.
Aynı anda birkaç çözüm çalıştırabilir miyim?
Evet, birkaç çözücü çağrısı üzerinde Promise.all ile. Node SDK’sında AsyncCapSkip, CapSkip için yalnızca başka bir addır, bu yüzden düz istemciye bir şey katmaz. CapSkip’in kendi sınırını göz önünde bulundurun: reCAPTCHA Max. Threads ayarı varsayılan olarak 10’dur ve fazla işler boş bir thread bekler. Eşzamanlılık bu rehber için Bun altında ölçülmedi.
Barındırılan bir platformdaki script CapSkip’imi kullanabilir mi?
Barındırılan bir platform başkasının makinesinde çalışır, bu yüzden bilgisayarınızdaki 127.0.0.1’e ulaşamaz. CapSkip’i kontrol ettiğiniz bir Windows makinesinde, statik bir genel IP ile Server modunda çalıştırın. Bu adresi .env dosyasına ya da platformun gizli bilgi deposuna koyun. CapSkip portunda zaten her adrese izin verir, bu yüzden geri kalanı için bir engelleme kuralı ekleyin ve anahtarın düz HTTP üzerinden açık metin olarak taşındığını unutmayın. Bu rehber için barındırılan hiçbir platform test edilmedi.
Kısa özet
Bun’da reCAPTCHA çözmek için bun add’den önce bun init çalıştırın. CapSkip ayarlarını bir .env dosyasında tutun ve istemciye kendiniz verin. Her çözümü bir zamanlayıcıyla yarıştırın ve script’i process.exit ile bitirin; böylece takılı bir socket onu ayakta tutamaz. Beş saniyelik test sınırını yükseltin ve canlı testin yanında fake çözücülü bir test bulundurun.
- Node.js paketinin desteklediği tüm CAPTCHA türleri: Node.js CAPTCHA çözücü sayfası.
- Onay kutulu çözücünün nasıl çalıştığı: reCAPTCHA v2 çözücü sayfası.
- Aynı görev başka bir çalışma zamanında: Deno’da reCAPTCHA çözme.
Gecede birkaç yüz kez çözüm yapan bir script çabuk birikir. Bunu kendi Windows makinenizde çalışan bir sınırsız captcha çözücü üzerinden yürütmek, bu çözümlerin asla bir faturaya dönüşmemesi demektir.
