Bun में .env फ़ाइलों और bun test के साथ reCAPTCHA कैसे हल करें

Bun में reCAPTCHA हल करने के लिए, bun add से CapSkip Node.js SDK जोड़ें। क्लाइंट को उन वैल्यू से बनाएँ जो Bun पहले ही आपकी .env फ़ाइल से लोड कर चुका है, फिर सॉल्वर को कॉल करें और token पोस्ट करें। CapSkip आपके अपने Windows PC पर चलता है, इसलिए कोई प्रति-हल बिल नहीं बनता। कोड छोटा है, और SDK Bun में जैसा है वैसा ही काम करता है। Bun की तीन आदतें वैसी नहीं हैं जैसी Node डेवलपर उम्मीद करता है। Bun .env फ़ाइलों को बिना कहे पढ़ लेता है, और सिर्फ़ उसी फ़ोल्डर से जिसमें आप उसे चलाते हैं। अटके हुए socket पर लगा timeout reject तो होता है, पर प्रोसेस को खत्म नहीं करता। और bun test हर टेस्ट को पाँच सेकंड के बाद रोक देता है, जो असली हल से छोटा है। यह गाइड इन तीनों को कवर करती है।
आपको क्या चाहिए
- किसी Windows PC पर चल रहा CapSkip, जिसमें reCAPTCHA सॉल्वर चालू हो। पहली बार शुरू करने के चरण सेटअप गाइड में समझाए गए हैं।
- Windows के लिए Bun। नीचे का सब कुछ Windows 11 पर Bun 1.4.3 और npm के capskip 1.3.0 के साथ चलाया गया। Bun के पुराने वर्ज़न टेस्ट नहीं किए गए।
- एक PowerShell prompt। कमांड PowerShell syntax में लिखी गई हैं।
- एक ऐसा पेज जिसके फॉर्म में reCAPTCHA v2 checkbox हो। उदाहरण Django जैसे एक फॉर्म में लॉग इन करता है जिसमें CSRF token होता है। फ़ील्ड के नाम अपनी साइट के हिसाब से बदलें।
- सॉल्वर के लिए एक पता। Local मोड में CapSkip केवल उसी डिवाइस के लिए 127.0.0.1 पर जवाब देता है। Server मोड में वह आपके नेटवर्क पते या पब्लिक IP पर सुनता है, ताकि किसी दूसरी मशीन पर चल रही script API के ज़रिए उसे कॉल कर सके। दोनों यहाँ सेट किए जाते हैं: कनेक्शन सेटिंग्स.
नीचे की माप एक ही PC पर चल रहे mock CapSkip और mock लॉगिन पेज से ली गई हैं (यानी टेस्ट के लिए बनाए गए नकली CapSkip और नकली पेज)। Bun और SDK असली हैं। आपकी अपनी साइट पर Bun और SDK का व्यवहार वैसा ही होना चाहिए, लेकिन टेस्ट सिर्फ़ एक mock के साथ हुआ। हल करने का समय और लॉगिन पेज के स्वीकार व अस्वीकार करने के नियम mock के अपने हैं।
चरण 1: Bun इंस्टॉल करें
Bun का अपना इंस्टॉलेशन पेज नीचे की कमांड देता है, और वह npm install -g bun भी बताता है। इस गाइड के टेस्ट में Bun 1.4.3 इस्तेमाल हुआ, जिसे npm से एक प्रोजेक्ट फ़ोल्डर में इंस्टॉल किया गया था। installer की कमांड खुद यहाँ चलाई नहीं गई। बाद में एक नई PowerShell विंडो खोलें, ताकि PATH रिफ़्रेश हो जाए।
# 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: पहले प्रोजेक्ट बनाएँ
bun add से पहले bun init चलाएँ। अगर फ़ोल्डर में package.json नहीं है, तो bun add ऊपर की ओर सबसे नज़दीकी package.json खोजता है और उसी फ़ाइल को बदल देता है। एक नकली parent फ़ोल्डर के अंदर बने टेस्ट फ़ोल्डर पर, उसने parent के दूसरे पैकेज वैसे ही रखे और capskip को parent में जोड़ दिया। आपका अपना parent फ़ोल्डर कोई प्रोफ़ाइल फ़ोल्डर या monorepo का root हो सकता है। प्रोजेक्ट फ़ाइल पहले बना लेने से इंस्टॉल वहीं रहता है जहाँ आप उम्मीद करते हैं।
# Make the folder, then create package.json BEFORE bun add. mkdir captcha-bun cd captcha-bun bun init -y bun add capskip
init कमांड एक strict tsconfig.json, एक index.ts, एक README.md, एक .gitignore और module type सेट वाली एक package.json लिखती है। वह TypeScript और Bun के type definitions भी इंस्टॉल करती है। add कमांड capskip को वर्ज़न 1.3.0 या उसके बाद के रूप में सेव करती है और bun.lock नाम की एक टेक्स्ट lockfile लिखती है। SDK CommonJS है। नीचे इस्तेमाल हुआ named import और require, दोनों काम करते हैं, जो Deno से अलग है, जहाँ require फेल होता है।
init छोड़ने की एक दूसरी कीमत भी है। Bun किसी import को फ़ोल्डरों में ऊपर की ओर चलकर resolve करता है। आपके प्रोजेक्ट में node_modules न होने पर, उसने टेस्ट PC पर parent फ़ोल्डर की node_modules से पुराना capskip 1.0.2 लोड कर लिया। किसी error ने यह नहीं बताया।
चरण 3: सेटिंग्स को .env फ़ाइल में रखें
प्रोजेक्ट फ़ोल्डर में .env नाम की एक फ़ाइल बनाएँ। Bun उसे आपका कोड चलने से पहले लोड कर लेता है, इसलिए वैल्यू Bun.env और process.env में होती हैं, और दोनों हमेशा एक जैसी रहती हैं। SDK कोई एनवायरनमेंट वेरिएबल खुद नहीं पढ़ता। आपकी script तीनों वैल्यू पढ़ती है और उन्हें पास करती है, जो अगला चरण है। LOGIN_URL वाली लाइन नीचे के live टेस्ट के लिए है।
# .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
नियम जान लें, क्योंकि कोई भटकी हुई .env फ़ाइल आपको चौंका सकती है:
- Bun .env फ़ाइलें सिर्फ़ उसी फ़ोल्डर से पढ़ता है जिसमें आप उसे चलाते हैं। किसी दूसरे फ़ोल्डर से script को उसके path से चलाने पर कुछ नहीं पढ़ा जाता, और एक फ़ोल्डर ऊपर की .env भी नहीं पढ़ी जाती।
- शेल में सेट किया गया वेरिएबल जीतता है। फिर .env.local आता है, फिर मोड वाली फ़ाइल, जो bun run के तहत .env.development है जब तक NODE_ENV सेट न हो, और फिर सादी .env।
- bun init की .gitignore .env और .env.local को कवर करती है। वह .env.production, .env.test या .env.development को कवर नहीं करती, इसलिए commit करने से पहले उन्हें जोड़ दें।
- फ़ाइल में quotes, पीछे की comment और ${OTHER} expansion काम करते हैं।
चरण 4: CapSkip क्लाइंट बनाएँ
अगले ब्लॉक एक के बाद एक solve.ts नाम की फ़ाइल में सेव करें। चरण 4 से 9 सब उसी एक फ़ाइल में जाते हैं, और पूरी फ़ाइल अंत के पास दिखाई गई है। डिफ़ॉल्ट वैल्यू की वजह से script बिना किसी सेटअप के चल जाती है। CAPSKIP_API_KEY तभी मायने रखता है जब आपने CapSkip में API key validation चालू किया हो।
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: cookies खुद सँभालें
लॉगिन फॉर्म को उस पेज की cookie चाहिए जिसने उसे दिखाया, और Bun का fetch कोई cookie नहीं रखता। हर Set-Cookie लाइन को getSetCookie से पढ़ें। आम headers.get कॉल लाइनों को कॉमा से जोड़ देती है, और Expires तारीख के अंदर का कॉमा फिर split को बिगाड़ देता है।
// 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: sitekey और CSRF token पढ़ें
यह हिस्सा आपका अपना है, CapSkip का नहीं। यह लॉगिन पेज खोलता है और दोनों वैल्यू निकालता है। patterns को अपने markup के हिसाब से बदलें।
// 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: ऐसा timeout जोड़ें जो काम करे
SDK कोई socket timeout नहीं लगाता। जो CapSkip कॉल स्वीकार करके कभी जवाब नहीं देता, वह script को अटका देता है। टेस्टिंग में, Bun ने कुछ प्रिंट नहीं किया और कभी बंद नहीं हुआ, और 400 सेकंड बाद भी इंतज़ार कर रहा था। SDK का timeout ऑप्शन सिर्फ़ poll के बीच जाँचा जाता है, इसलिए जब कोई एक रिक्वेस्ट अटकी हो तब वह मदद नहीं कर सकता। नीचे का ब्लॉक हल को एक 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, Deno से अलग है, और आपके फ़ायदे में। AbortSignal.timeout का timer Bun को चालू नहीं रखता, इसलिए सामान्य हल पूरा होते ही बंद हो जाता है। टेस्टिंग में, 1.8 सेकंड के हल के आसपास 330 सेकंड की समय-सीमा वाली script करीब 1.8 सेकंड पर खत्म हो गई। एक सादा setTimeout जिसे आप कभी clear नहीं करते, अलग बात है। उसने Bun को तब तक चालू रखा जब तक वह चला नहीं, यानी 20 सेकंड की समय-सीमा के लिए 20.04 सेकंड। ऐसे timer को finally block में clear करें या signal इस्तेमाल करें।
पेंच उलटी दिशा में है। जब अटके हुए socket पर समय-सीमा खत्म होती है, तो promise तुरंत reject हो जाता है, 3 सेकंड की समय-सीमा वाले टेस्ट में 3.0 सेकंड पर। उसके बाद भी Bun चालू रहता है, क्योंकि अटका हुआ socket उसे खुला रखता है। इसीलिए चरण 9 process.exit पर खत्म होता है। यही तब भी होता है जब timer पहले चल जाए (यानी दौड़ हार जाए) और CapSkip तब भी अभी तैयार नहीं का जवाब दे रहा हो। SDK बैकग्राउंड में poll करता रहता है, और प्रोसेस SDK के अपने timeout तक जीवित रहता है, जो 12 की सीमा के साथ 12.8 सेकंड था।
अगर आपकी script को जल्दी फेल होना है तो छोटी समय-सीमा चुनें। टेस्ट में ब्लॉक को 4 सेकंड के साथ चलाया गया, एक ऐसे CapSkip के साथ जो पहले submit पर और फिर poll पर चुप हो गया। दोनों ने चरण 9 का संदेश प्रिंट किया और करीब 4.04 सेकंड पर exit code 1 के साथ बंद हुए। सामान्य हल करीब 1.8 सेकंड पर exit code 0 के साथ बंद हुआ।
चरण 8: हल करें, पोस्ट करें और नतीजा जाँचें
अगला ब्लॉक लॉगिन है। reCAPTCHA token एक ही बार काम करता है, इसलिए हर लॉगिन अपना हल खुद करता है। दो बातें मायने रखती हैं। HTTPS पर Django जैसी सख़्त साइट बिना Referer वाली फॉर्म पोस्ट को ठुकरा देती है, और fetch कोई Referer नहीं भेजता, इसलिए ब्लॉक उसे सेट करता है। और fetch डिफ़ॉल्ट रूप से 302 को फ़ॉलो करता है और रास्ते में नई cookie खो देता है। इसलिए ब्लॉक redirect को manual पर सेट करता है और cookie 302 से पढ़ता है। फ़ील्ड के नाम, username और Welcome back वाला marker अपनी साइट के हिसाब से बदलें।
// 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: errors की रिपोर्ट करें और इसे चलाएँ
बंद पोर्ट, CapSkip की error और धीमा हल, तीन अलग exception classes हैं, और instanceof Bun में उन्हें अलग-अलग पहचान लेता है। बंद पोर्ट पर करीब 10 मिलीसेकंड में NetworkException आया, जिसका संदेश connect ECONNREFUSED 127.0.0.1 और पोर्ट था। host नाम localhost लगभग उतना ही तेज़ था, पर उसने संदेश खाली छोड़ा, और error code cause पर था। ब्लॉक उसी code को इस्तेमाल करने लगता है।
// 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);
}
}उदाहरण वाले URL को अपने लॉगिन पेज से बदलें, और इसे प्रोजेक्ट फ़ोल्डर से चलाएँ। Bun वहीं की .env फ़ाइल उठा लेता है। PowerShell में सेट किया गया वेरिएबल फ़ाइल में उसी नाम पर भारी पड़ता है, और वह उस PowerShell सेशन के बाकी समय तक सेट रहता है, इसलिए ब्लॉक उसे फिर से साफ़ कर देता है। आख़िरी दो कमांड automatic loading को बंद करती हैं, या इसके बजाय आपकी चुनी हुई फ़ाइल पढ़ती हैं। वह फ़ाइल पहले बना लें। गायब फ़ाइल बिना संदेश के छोड़ दी जाती है, और .env भी नहीं पढ़ी जाती।
# 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
एक अच्छा रन logged in प्रिंट करता है और exit code 0 के साथ बंद होता है। mock के सामने, जिस हल को mock ने एक सेकंड रोके रखा, उसमें 1.77 सेकंड लगे। SDK तुरंत poll करता है, फिर 0.25, 0.5, 1, 2 और 4 सेकंड रुकता है, और उसके बाद 5 सेकंड। छोटा हल गोल करके सीधे पाँच नहीं हो जाता।
bun test से इसे टेस्ट करें
Bun के साथ उसका अपना test runner आता है। वह solve.test.ts जैसे नाम वाली फ़ाइलें चलाता है, और उसकी डिफ़ॉल्ट सीमा हर टेस्ट के लिए 5000 मिलीसेकंड है। असली reCAPTCHA हल इससे ज़्यादा ले सकता है। जो टेस्ट 8 सेकंड वाले mock का इंतज़ार करता रहा, वह इस संदेश के साथ फेल हुआ, और रन exit code 1 के साथ बंद हुआ:
- (fail) logs in through CapSkip [5001.15ms]
- this test timed out after 5000ms.
सीमा तीन तरीकों में से किसी एक से बढ़ाएँ: test का तीसरा argument, नीचे की तरह setDefaultTimeout, या bun test का timeout switch। तीनों 8 सेकंड वाले mock के सामने पास हुए। bunfig.toml के test heading के नीचे की timeout key काम नहीं की। टेस्ट फिर भी 5000 मिलीसेकंड पर फेल हुआ, और Bun के bunfig दस्तावेज़ में ऐसी कोई key नहीं है। live टेस्ट LOGIN_URL को .env फ़ाइल से पढ़ता है, इसलिए उसे अपने लॉगिन पेज पर सेट करें। इसे 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);
});आपके ज़्यादातर suite को सॉल्वर की ज़रूरत ही नहीं होनी चाहिए। दूसरा टेस्ट SDK को एक fake से बदल देता है और एक छोटी नकली साइट के सामने चलता है, इसलिए वह CapSkip बंद होने पर भी पास होता है। दो बातों में गलती करना आसान है। solve.ts का import dynamic होना चाहिए, क्योंकि static import mock.module से पहले चल जाता है। और fake लीक होता है। mock दूसरी टेस्ट फ़ाइलों के लिए भी लागू रहता है। सादे bun test ने solve.test.ts को login rejected: 403 के साथ फेल कर दिया, क्योंकि वह अब fake सॉल्वर इस्तेमाल कर रहा था। इसे 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();
});फ़िक्स isolate switch है, जो हर फ़ाइल को एक नया global object देता है। उसके साथ दोनों फ़ाइलें पास हुईं। Bun, bun test के दौरान NODE_ENV को test पर भी सेट करता है, इसलिए वह .env.test लोड करता है और .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
एक .exe बनाएँ
compile कमांड Bun runtime, आपकी script और SDK को एक ही फ़ाइल में रख देती है। यहाँ इसमें 0.3 सेकंड लगे और 96.4 MB की फ़ाइल बनी, जो खुद Bun जितनी ही है। exe ऐसे PATH के साथ चली जिसमें Bun था ही नहीं। .env फ़ाइल exe के अंदर पक्की नहीं होती। exe रन के समय उस फ़ोल्डर की .env पढ़ती है जिससे वह चल रही है। वह build फ़ोल्डर वाली .env या exe के बगल वाली .env इस्तेमाल नहीं करती। जिस फ़ोल्डर में .env नहीं है, वहाँ से शुरू करने पर port undefined आता है, और चरण 4 के डिफ़ॉल्ट लागू होते हैं। Task Scheduler में Start in फ़ोल्डर को उस फ़ोल्डर पर सेट करें जिसमें आपकी .env है। build कमांड में no-compile-autoload-dotenv switch जोड़ दें तो exe उस फ़ाइल को अनदेखा कर देती है। असली वेरिएबल फिर भी काम करते हैं।
# 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
types की जाँच ज़रूरत पड़ने पर करें
bun run कमांड types हटा देती है और उन्हें कभी जाँचती नहीं। type error वाली फ़ाइल भी चली और लॉग इन कर गई। Bun 1.4.3 में bun check कमांड है जो प्रोजेक्ट पर TypeScript चलाती है, और दोनों टेस्ट फ़ाइलें साफ़ निकलीं। SDK की तीन typing बातें जानने लायक हैं। code फ़ील्ड को string या object घोषित किया गया है, इसीलिए चरण 7 उसे String में लपेटता है। token फ़ील्ड वैकल्पिक है। और recaptcha कॉल का options type कोई भी नाम स्वीकार करता है, इसलिए गलत लिखा ऑप्शन compile हो जाता है। constructor का गलत लिखा ऑप्शन, जैसे hots, error है।
# bun run never type checks. Check on demand (TypeScript from bun init): bun check
script को दूसरी मशीन पर चल रहे CapSkip के सामने चलाएँ
script और सॉल्वर का एक ही PC पर होना ज़रूरी नहीं। कनेक्शन सेटिंग्स में CapSkip को Server मोड पर कर दें, तो वह आपके नेटवर्क पते या पब्लिक IP पर सुनता है। वह पता उस मशीन की .env में रखें जो script चलाती है। भरोसेमंद रिमोट एक्सेस के लिए स्टैटिक पब्लिक IP की सलाह दी जाती है। सॉल्वर अब भी आपकी अपनी Windows मशीन है, और हल अब भी बिना मीटर वाले हैं।
# .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
दो बातों का ध्यान रखें। SDK, CapSkip से सादे HTTP पर बात करता है, इसलिए API key clear text में जाती है। उस लिंक को निजी नेटवर्क या VPN पर रखें, और API key validation चालू करें। और Server मोड शुरू होते ही CapSkip अपना पोर्ट हर पते के लिए खोल देता है। पहुँच को सिर्फ़ अपेक्षित मशीनों तक सीमित रखने के लिए, बाकी पतों के लिए Windows Firewall में एक block नियम जोड़ें। इस गाइड के लिए रिमोट सेटअप चलाया नहीं गया। सिर्फ़ .env फ़ाइल को parse किया गया।
पूरी solve.ts
यह चरण 4 से 9 एक ही फ़ाइल में है, जल्दी शुरू करने के लिए। अगर आपने ब्लॉक एक-एक करके सेव किए थे, तो यह आपके पास पहले से है। फ़ाइल को दो बार न चिपकाएँ, वरना Bun बताएगा कि कोई नाम पहले ही declare हो चुका है।
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 में reCAPTCHA हल करते समय आम errors
| आप जो देखते हैं | कारण | फिक्स |
|---|---|---|
| this test timed out after 5000ms | bun test हर टेस्ट को पाँच सेकंड पर काट देता है | setDefaultTimeout, या test का तीसरा argument |
| केवल solve.test.ts में login rejected: 403 | किसी दूसरी फ़ाइल का mock.module अब भी लागू है | bun test को isolate switch के साथ चलाएँ |
| खाली संदेश वाला NetworkException | host कोई नाम है, जैसे localhost, इसलिए error cause पर है | err.cause.code प्रिंट करें, या 127.0.0.1 इस्तेमाल करें |
| NetworkException: connect ECONNREFUSED 127.0.0.1 | CapSkip चल नहीं रहा, या port गलत है | CapSkip शुरू करें और कनेक्शन सेटिंग्स में port जाँचें |
| Object is not a constructor | default import पूरा exports object दे देता है | CapSkip को नाम से import करें, curly braces में |
| script अटक जाती है और कुछ प्रिंट नहीं करती | CapSkip ने कॉल स्वीकार कर ली और चुप हो गया | withTimeout वाली दौड़ इस्तेमाल करें और process.exit पर खत्म करें |
| गलत capskip वर्ज़न लोड होता है | प्रोजेक्ट में node_modules नहीं है, इसलिए Bun को ऊपर कहीं पुरानी कॉपी मिल गई | bun init और bun add प्रोजेक्ट फ़ोल्डर के अंदर चलाएँ |
| exe में सेटिंग्स undefined आती हैं | exe अपने working फ़ोल्डर की .env पढ़ती है | उसे उसी फ़ोल्डर से चलाएँ, या Start in सेट करें |
| 127.0.0.1 पर fetch ECONNREFUSED के साथ फेल होता है | HTTP_PROXY ऐसे proxy की ओर इशारा करता है जो बंद है, और fetch उसे इस्तेमाल करता है | NO_PROXY को 127.0.0.1,localhost पर सेट करें |
| ApiException: ERROR_CAPTCHA_UNSOLVABLE | CapSkip यह हल नहीं कर सका | हल दोबारा आज़माएँ |
FAQ
क्या CapSkip Node.js SDK Bun में काम करता है?
हाँ, Bun 1.4.3 पर capskip 1.3.0 के साथ। import, पूरा हल, चारों exception classes और polling, सब वैसे ही चले जैसे Node में चलते हैं। उसी mock के सामने हल में Bun 1.4.3 और Node 25, दोनों में करीब 1.8 सेकंड लगे। इस सेटअप के सादे संस्करण के लिए यह गाइड देखें: Node.js में reCAPTCHA v2 हल करना.
यह Deno से कैसे अलग है?
Deno तब तक नेटवर्क और फ़ाइल access रोके रखता है जब तक आप अनुमति न दें, और AbortSignal timer Deno को चालू रखता है। Bun में permission flags नहीं हैं, इसलिए अनुमति देने को कुछ नहीं है। जाल ये हैं: .env की अपने-आप होने वाली लोडिंग, अटका हुआ socket जो timeout के बाद भी बचा रहता है, और पाँच सेकंड की टेस्ट सीमा। permission वाले पहलू को Deno गाइड कवर करती है।
क्या मैं एक साथ कई सॉल्व चला सकता हूँ?
हाँ, कई solver कॉल पर Promise.all के साथ। Node SDK में AsyncCapSkip सिर्फ़ CapSkip का दूसरा नाम है, इसलिए यह सादे क्लाइंट पर कुछ नहीं जोड़ता। CapSkip की अपनी सीमा का ध्यान रखें: reCAPTCHA Max. Threads सेटिंग डिफ़ॉल्ट रूप से 10 है, और अतिरिक्त जॉब खाली thread का इंतज़ार करती हैं। इस गाइड के लिए Bun में concurrency को मापा नहीं गया।
क्या किसी होस्टेड प्लेटफ़ॉर्म की script मेरे CapSkip का इस्तेमाल कर सकती है?
होस्टेड प्लेटफ़ॉर्म किसी और की मशीन पर चलता है, इसलिए वह आपके PC के 127.0.0.1 तक नहीं पहुँच सकता। CapSkip को अपने नियंत्रण वाली किसी Windows मशीन पर, स्टैटिक पब्लिक IP के साथ, Server मोड में चलाएँ। वह पता .env में या प्लेटफ़ॉर्म के secret store में रखें। CapSkip अपने पोर्ट पर पहले से हर पते को अनुमति देता है, इसलिए बाकी के लिए एक block नियम जोड़ें, और याद रखें कि key सादे HTTP पर clear text में जाती है। इस गाइड के लिए कोई होस्टेड प्लेटफ़ॉर्म टेस्ट नहीं किया गया।
संक्षेप में
Bun में reCAPTCHA हल करने के लिए, bun add से पहले bun init चलाएँ। CapSkip की सेटिंग्स .env फ़ाइल में रखें और उन्हें क्लाइंट को खुद पास करें। हर हल को एक timer के साथ दौड़ाएँ, और script को process.exit पर खत्म करें, ताकि अटका हुआ socket उसे चालू न रख सके। पाँच सेकंड की टेस्ट सीमा बढ़ाएँ, और live टेस्ट के बगल में fake-solver टेस्ट भी रखें।
- कैप्चा के वे सभी टाइप जिन्हें Node.js पैकेज संभालता है: Node.js कैप्चा सॉल्वर पेज.
- checkbox वाला सॉल्वर कैसे काम करता है: reCAPTCHA v2 सॉल्वर पेज.
- दूसरे runtime पर यही काम: Deno में reCAPTCHA हल करना.
जो script हर रात कुछ सौ बार हल करती है, उसकी गिनती जल्दी ही बढ़ जाती है। अपनी खुद की Windows मशीन पर चल रहे असीमित कैप्चा सॉल्वर से इसे चलाने का मतलब है कि ये हल कभी बिल में नहीं बदलते।
