Cara Memecahkan Friendly Captcha di Node.js Tanpa Browser

solve friendly captcha in node.js - How to Solve Friendly Captcha in Node.js Without a Browser

Untuk memecahkan Friendly Captcha di Node.js, ambil halamannya dan simpan cookie yang disetelnya, baca widget frc-captcha beserta tag script-nya dengan cheerio, tentukan v1 atau v2 dari skrip itu, lalu berikan sitekey, URL halaman, versi, dan alamat skrip ke metode friendlyCaptcha milik CapSkip. Kirim token yang dikembalikannya bersama field tersembunyi milik form itu sendiri, di frc-captcha-response pada v2 atau frc-captcha-solution pada v1. Dua hal menjadi penyebab sebagian besar kegagalan di sini. Fungsi fetch bawaan tidak menyimpan cookie, sehingga form yang dilindungi CSRF menolak token yang sempurna sekalipun. Selain itu, memecahkan versi yang salah menghasilkan token yang tampak valid tetapi gagal tanpa pesan apa pun. CapSkip menambahkan Friendly Captcha pada versi 1.4.0, dan panduan ini menjalankan seluruh prosesnya tanpa browser di sisi Anda.

Apa yang Anda butuhkan

  • CapSkip 1.4.0 atau lebih baru yang berjalan di komputer Windows. Rilis itu menambahkan Friendly Captcha, bersama CaptchaFox dan Capy Puzzle.
  • Node.js 22 atau lebih baru dan paket capskip versi 1.3.0 atau lebih baru, rilis pertama yang memiliki friendlyCaptcha. Contoh kodenya juga memakai cheerio untuk membaca HTML.
  • URL halaman yang menampilkan widget. Sitekey, alamat skrip, dan nama field semuanya diambil dari HTML halaman itu.
  • Sebuah alamat untuk pemecah CAPTCHA. Mode Local menjawab di 127.0.0.1 hanya untuk perangkat itu; mode Server mendengarkan di alamat jaringan atau IP publik Anda sehingga skrip di mesin lain dapat memanggilnya melalui API. Keduanya ada di pengaturan koneksi, dan Langkah 4 membahas kapan harus beralih.
# npm install capskip cheerio
npm install capskip cheerio

Contoh kodenya berupa modul ES dengan top-level await. Simpan dengan ekstensi .mjs, atau ubah field type di package.json Anda menjadi module, karena npm init sekarang menulis commonjs di sana, dan import akan berfungsi seperti yang ditunjukkan. Paket capskip juga bisa dimuat dengan require jika proyek Anda memakai CommonJS.

Langkah 1: ambil halaman dan simpan cookie-nya

Fungsi fetch milik Node adalah klien HTTP yang bagus dengan satu kekurangan yang penting di sini: ia tidak punya cookie jar. Setiap pemanggilan dimulai dari kosong, sehingga cookie sesi yang disetel halaman sudah hilang saat Anda mengirim form. Situs yang mengikat token CSRF-nya ke sesi itu lalu menolak kiriman tersebut, sering kali dengan 403, sebagus apa pun token CAPTCHA-nya. Di Python, requests.Session menyembunyikan masalah ini, tetapi di Node Anda harus membawa cookie itu sendiri.

Alat untuk itu adalah Headers.getSetCookie(), yang mengembalikan setiap baris Set-Cookie secara terpisah. Pemanggilan headers.get biasa tidak bisa melakukan tugas itu, karena ia menggabungkan semua baris itu dengan koma, padahal tanggal kedaluwarsa cookie juga mengandung koma.

// npm install capskip cheerio
import * as cheerio from "cheerio";

const PAGE_URL = "https://example.com/signup";

// fetch() keeps no cookies between calls, so carry them by hand.
const jar = new Map();
function remember(res) {
  for (const line of res.headers.getSetCookie()) {
    const pair = line.split(";")[0];
    const eq = pair.indexOf("=");
    jar.set(pair.slice(0, eq).trim(), pair.slice(eq + 1).trim());
  }
}
const cookieHeader = () => [...jar].map(([k, v]) => `${k}=${v}`).join("; ");

const page = await fetch(PAGE_URL);
remember(page);
const $ = cheerio.load(await page.text());

Cookie jar itu sengaja dibuat kecil. Ia menyimpan nama dan nilai serta mengabaikan path dan masa berlaku, dan itu sudah cukup untuk satu situs dan satu form.

Langkah 2: baca widget, pilih v1 atau v2, lalu panggil friendlyCaptcha

Kedua versi merender elemen yang sama, yaitu sebuah div dengan class frc-captcha dan atribut data-sitekey, sehingga widget itu sendiri tidak memberi tahu apa pun tentang protokolnya. Tag script-lah yang memberi tahu. Situs v2 memuat paket @friendlycaptcha/sdk, yang file-nya adalah site.min.js, dan situs v1 memuat friendly-challenge, yang file-nya adalah widget.module.min.js. Cheerio mengambil keduanya hanya dalam beberapa baris:

const widget = $(".frc-captcha").first();
const form = widget.closest("form");

// Every script except nomodule fallbacks, as full addresses.
const scripts = $("script[src]").not("[nomodule]")
  .map((_, el) => new URL($(el).attr("src"), PAGE_URL).href)
  .get();

Memberikan URL halaman sebagai base penting untuk widget yang di-host sendiri. Itu mengubah src relatif seperti /vendor/v2/site.min.js menjadi alamat lengkap, dan pada v2 CapSkip memuat skrip persis itu di browser miliknya sendiri untuk menjalankan pemecahan. Path mentah tidak akan pernah dimuat.

CapSkip memilih versi dengan urutan tetap dan berhenti pada jawaban pertama: versi yang Anda berikan, lalu alamat skrip yang Anda berikan sebagai moduleScript, lalu default v1. Default itulah jebakannya. Skrip hasil bundling seperti /assets/app.4f2a.js tidak memberi tahu CapSkip apa pun, sehingga CapSkip memecahkannya sebagai v1 dan situs v2 menolak setiap token. Sebaiknya tentukan sendiri di kode Anda, tempat Anda bisa menolak untuk menebak:

const V2_FILES = ["site.min.js", "site.compat.min.js"];
const V1_FILES = ["widget.module.min.js", "widget.min.js", "widget.polyfilled.min.js"];

function friendlyVersion(scripts, widget) {
  // Package names are unambiguous, so check them first.
  for (const src of scripts) {
    if (src.includes("@friendlycaptcha/sdk")) return { version: "v2", script: src };
    if (src.includes("friendly-challenge")) return { version: "v1", script: src };
  }
  // Self-hosted builds usually keep the file name. Themes ship their own
  // site.min.js too, so only trust a path that says friendly.
  for (const src of scripts.filter((s) => s.toLowerCase().includes("friendly"))) {
    const file = new URL(src).pathname.split("/").pop();
    if (V2_FILES.includes(file)) return { version: "v2", script: src };
    if (V1_FILES.includes(file)) return { version: "v1", script: src };
  }
  // Last resort: v2 and v1 name their widget options differently.
  if (widget.is("[data-api-endpoint], [data-form-field-name]")) return { version: "v2" };
  if (widget.is("[data-puzzle-endpoint], [data-solution-field-name]")) return { version: "v1" };
  throw new Error("v1 or v2? Read the page and set it by hand.");
}

Pemeriksaan nama file hanya memercayai path yang menyebut friendly, karena sebuah tema bisa memuat site.min.js miliknya sendiri yang sama sekali tidak berhubungan dengan widget.

Lalu lakukan pemanggilannya. Metode friendlyCaptcha menerima sitekey, URL halaman, dan sebuah objek opsi. SDK membuang nilai undefined sebelum mengirim apa pun, sehingga atribut yang tidak dimiliki halaman itu tidak ikut masuk ke permintaan:

import { CapSkip } from "capskip";

const solver = new CapSkip({ host: "127.0.0.1", port: 8080 });
const { version, script } = friendlyVersion(scripts, widget);

const result = await solver.friendlyCaptcha(widget.attr("data-sitekey"), PAGE_URL, {
  version,
  moduleScript: script,
  // EU sites: data-api-endpoint="eu" (v2) or data-puzzle-endpoint (v1)
  apiServer: widget.attr("data-api-endpoint") ?? widget.attr("data-puzzle-endpoint"),
});

console.log(result.token.slice(0, 40));   // v2 tokens start with AQQA.

Baris apiServer ditujukan untuk situs yang memakai endpoint EU milik Friendly Captcha. Endpoint global tetap menerbitkan token untuk sitekey EU, jadi memecahkannya di sana baru gagal saat pemeriksaan milik situs itu sendiri, kegagalan diam-diam yang sama seperti pada versi yang salah. result.token adalah string yang harus dikirim, result.code berisi string yang sama, dan result.captchaId adalah id CapSkip untuk job tersebut. Versi selain v1, v2, 1 atau 2, sitekey yang kosong, atau opsi yang tidak dikenal metode ini akan melempar ValidationException sebelum permintaan apa pun meninggalkan mesin Anda.

Langkah 3: kirim token bersama field milik form itu sendiri

Field token tidak ada di HTML yang Anda ambil, karena skrip widget membuatnya di browser. Jadi Anda menambahkannya sendiri, dengan nama yang dipakai oleh versi dan widget. Semua hal lain yang dibawa form, termasuk token CSRF tersembunyi, berasal dari form itu sendiri:

// A renamed field wins; otherwise the default for the version.
const field = widget.attr("data-form-field-name")
  ?? widget.attr("data-solution-field-name")
  ?? (version === "v2" ? "frc-captcha-response" : "frc-captcha-solution");

// The form's own fields, hidden CSRF token included.
const body = new URLSearchParams(
  form.serializeArray().map((f) => [f.name, f.value]),
);
body.set("email", "YOUR_EMAIL");
body.set(field, result.token);

const res = await fetch(new URL(form.attr("action") || PAGE_URL, PAGE_URL), {
  method: "POST",
  // Browsers send the page as Referer; some servers refuse a post without it.
  headers: { cookie: cookieHeader(), referer: PAGE_URL },
  body,
});
console.log(res.status);

serializeArray mengumpulkan field yang akan dikirim oleh browser, dan begitulah token CSRF kembali ke server tanpa Anda perlu menyebut namanya. Tombol submit tidak ikut dikumpulkan, jadi jika situs memeriksa nama tombol itu, tambahkan dengan body.set. Kirim juga URL halaman sebagai Referer: fetch tidak mengirim header Referer maupun Origin, dan sebagian framework, termasuk Django, menolak form post HTTPS yang tidak memiliki keduanya, meskipun cookie dan tokennya sudah benar. Body URLSearchParams membuat fetch mengirim form post biasa dan menyetel content type untuk Anda. Token v2 berukuran sekitar enam kilobyte, jadi tempatnya di body itu dan jangan pernah di query string, sedangkan token v1 terdiri dari empat bagian yang dipisahkan titik dan panjangnya beberapa ratus karakter. Teruskan apa adanya.

Dua kasus perlu diperiksa di DevTools. Pada v1, data-solution-field-name yang diisi satu tanda hubung berarti widget sama sekali tidak menulis field, dan skrip milik situs itu sendiri mengirim token dengan cara lain. Sebagian situs juga mengirim JSON dari JavaScript alih-alih men-submit form. Untuk keduanya, kirim sekali secara manual lalu salin permintaan yang benar-benar dibuat halaman itu.

Setiap token hanya berlaku untuk satu pengiriman. Verifikasi Friendly Captcha menolak respons yang sudah pernah dipakai atau sudah kedaluwarsa, jadi pecahkan ulang untuk setiap form.

Langkah 4: banyak form sekaligus, dan di mana pemecah CAPTCHA berjalan

Setiap metode CapSkip di Node sudah mengembalikan Promise, dan AsyncCapSkip hanyalah nama lain untuk class yang sama, sehingga konkurensi cukup ditangani dengan JavaScript biasa. Yang Anda butuhkan adalah batas. Dorong seratus pemanggilan lewat Promise.all, maka CapSkip akan mengantrekan semua yang melebihi pengaturan Max. Threads di bagian Friendly Captcha-nya, 10 secara default, dan di antrean itu sebuah tugas menunggu hingga 250 detik untuk mendapat thread kosong sebelum CapSkip menggagalkannya. Worker pool kecil menjaga agar paling banyak sepuluh pemanggilan berjalan bersamaan:

// Run fn over items with at most `limit` in flight. A failed item
// becomes its Error instead of rejecting the whole batch.
async function mapLimited(items, limit, fn) {
  const results = new Array(items.length);
  let next = 0;
  async function worker() {
    while (next < items.length) {
      const i = next++;
      results[i] = await fn(items[i]).catch((err) => err);
    }
  }
  await Promise.all(Array.from({ length: Math.min(limit, items.length) }, worker));
  return results;
}

// jobs: { sitekey, url, version, script, apiServer } objects from Step 2
const tokens = await mapLimited(jobs, 10, (job) =>
  solver.friendlyCaptcha(job.sitekey, job.url, {
    version: job.version,
    moduleScript: job.script,
    apiServer: job.apiServer,
  }).then((r) => r.token));

Waktu pemecahan akan bervariasi. Friendly Captcha menetapkan jumlah kerja per permintaan dan menaikkannya untuk alamat yang sudah sering dilihatnya, dan CapSkip memecahkan setiap widget v2 di browser sungguhan. Itulah sebabnya metode ini melakukan polling hingga recaptchaTimeout, 300 detik secara default, bukan defaultTimeout 120 detik. CapSkip juga punya penghitung waktunya sendiri: di pengaturan Friendly Captcha-nya, sebuah pemecahan mendapat Row Timeout 120 detik sebelum digagalkan. Anda bisa memberikan timeout di objek opsi untuk mengubah waktu tunggu SDK untuk satu pemanggilan. SDK harus bertahan lebih lama daripada waktu menunggu thread ditambah waktu pemecahan, dan dengan pengaturan default CapSkip, totalnya bisa mencapai 370 detik. Jadi samakan pool dengan Max. Threads atau berikan timeout yang lebih panjang, dan naikkan lagi setiap kali Anda menaikkan Row Timeout, atau pemanggilan berakhir dengan TimeoutException sementara CapSkip masih bekerja. Interval polling juga bisa disetel per pemanggilan, tetapi hanya sebagai polling_interval dalam snake case. Bentuk camelCase pollingInterval hanya untuk constructor, dan jika dipakai sebagai opsi per pemanggilan, ia melempar ValidationException. Jika pemecahan melambat selama proses yang panjang, tingkat kesulitan yang naik pada satu alamat biasanya menjadi penyebabnya, jadi berikan objek proxy dengan key type dan uri, atau siapkan pool proxy di CapSkip.

Contoh kode memakai 127.0.0.1 karena itu benar ketika Node dan pemecah CAPTCHA berada di mesin yang sama. Jalankan skrip di tempat lain, seperti VPS, container, atau CI runner, maka loopback menunjuk ke mesin itu sendiri, sehingga pemanggilan pertama melempar NetworkException dengan ECONNREFUSED. Alihkan CapSkip ke mode Server dan ia akan mendengarkan di alamat jaringan atau IP publik Anda, sehingga semua lingkungan itu dapat menjangkaunya melalui API yang sama. Gunakan IP publik statis jika rutenya melewati internet, dengan aturan firewall untuk alamat yang Anda harapkan. Perangkatnya tetap milik Anda sendiri dan tetap tanpa kuota. Klien tidak membaca variabel lingkungan dengan sendirinya, jadi baca CAPSKIP_HOST di kode Anda lalu teruskan ke klien, seperti yang dilakukan contoh lengkap di bawah.

Contoh lengkap yang berfungsi

Berikut seluruh prosesnya dalam satu file: cara lengkap paling singkat untuk memecahkan Friendly Captcha di Node.js hanya dari sebuah URL halaman.

// npm install capskip cheerio
// solve-friendly.mjs: run with node solve-friendly.mjs
import * as cheerio from "cheerio";
import { CapSkip, CapSkipError, ValidationException } from "capskip";

const PAGE_URL = "https://example.com/signup";
const V2_FILES = ["site.min.js", "site.compat.min.js"];
const V1_FILES = ["widget.module.min.js", "widget.min.js", "widget.polyfilled.min.js"];

const jar = new Map();
function remember(res) {
  for (const line of res.headers.getSetCookie()) {
    const pair = line.split(";")[0];
    const eq = pair.indexOf("=");
    jar.set(pair.slice(0, eq).trim(), pair.slice(eq + 1).trim());
  }
}
const cookieHeader = () => [...jar].map(([k, v]) => `${k}=${v}`).join("; ");

function friendlyVersion(scripts, widget) {
  for (const src of scripts) {
    if (src.includes("@friendlycaptcha/sdk")) return { version: "v2", script: src };
    if (src.includes("friendly-challenge")) return { version: "v1", script: src };
  }
  for (const src of scripts.filter((s) => s.toLowerCase().includes("friendly"))) {
    const file = new URL(src).pathname.split("/").pop();
    if (V2_FILES.includes(file)) return { version: "v2", script: src };
    if (V1_FILES.includes(file)) return { version: "v1", script: src };
  }
  if (widget.is("[data-api-endpoint], [data-form-field-name]")) return { version: "v2" };
  if (widget.is("[data-puzzle-endpoint], [data-solution-field-name]")) return { version: "v1" };
  throw new Error("v1 or v2? Read the page and set it by hand.");
}

const solver = new CapSkip({
  apiKey: process.env.CAPSKIP_API_KEY ?? "capskip",
  host: process.env.CAPSKIP_HOST ?? "127.0.0.1",
  port: Number(process.env.CAPSKIP_PORT ?? 8080),
});

const page = await fetch(PAGE_URL);
remember(page);
const $ = cheerio.load(await page.text());
const widget = $(".frc-captcha").first();
if (!widget.attr("data-sitekey")) {
  throw new Error("No frc-captcha widget in the HTML; it may be built by JS.");
}
const form = widget.closest("form");
const scripts = $("script[src]").not("[nomodule]")
  .map((_, el) => new URL($(el).attr("src"), PAGE_URL).href).get();

const { version, script } = friendlyVersion(scripts, widget);
let result;
try {
  result = await solver.friendlyCaptcha(widget.attr("data-sitekey"), PAGE_URL, {
    version,
    moduleScript: script,
    apiServer: widget.attr("data-api-endpoint") ?? widget.attr("data-puzzle-endpoint"),
  });
} catch (err) {
  if (err instanceof ValidationException) throw new Error(`not sent: ${err.message}`);
  if (err instanceof CapSkipError) throw new Error(`solve failed: ${err.name}: ${err.message}`);
  throw err;
}

const field = widget.attr("data-form-field-name")
  ?? widget.attr("data-solution-field-name")
  ?? (version === "v2" ? "frc-captcha-response" : "frc-captcha-solution");

// Post wherever the form's action points, with its other fields.
const body = new URLSearchParams(form.serializeArray().map((f) => [f.name, f.value]));
body.set("email", "YOUR_EMAIL");
body.set(field, result.token);

const res = await fetch(new URL(form.attr("action") || PAGE_URL, PAGE_URL), {
  method: "POST",
  // Browsers send the page as Referer; some servers refuse a post without it.
  headers: { cookie: cookieHeader(), referer: PAGE_URL },
  body,
});
console.log(res.status, version, field);

Versi ditentukan sekali dan dipakai dua kali, untuk pemecahan dan untuk nama field, sehingga keduanya tidak akan pernah bertentangan. Jika cheerio tidak menemukan widget, biasanya halaman membangunnya dari JavaScript, dan Anda perlu mengambil sitekey dari halaman yang sudah dirender atau dari skrip yang membuatnya. Setiap parameter yang diterima endpoint mentahnya tercantum di referensi API Friendly Captcha.

Kesalahan umum dan artinya

Apa yang Anda lihatPenyebabPerbaiki
403, atau error CSRF, dengan token yang dikembalikan CapSkipForm dikirim tanpa cookie halaman, field tersembunyinya, atau RefererKirim header cookie dari Langkah 1 dan Referer, lalu susun body dari serializeArray
Situs menolak token tanpa error lainVersi yang dipecahkan salah, sering kali v1 karena defaultTentukan versi di kode seperti di Langkah 2, lalu berikan
Ditolak di situs yang widget-nya memiliki data-api-endpoint (v2) atau data-puzzle-endpoint (v1)Token berasal dari endpoint global, padahal situs memakai endpoint EUBerikan nilai atribut itu sebagai apiServer
Pemecahan v2 gagal di situs yang meng-host widget-nya sendirimoduleScript berupa path relatif, sehingga widget tidak pernah dimuatUbah src menjadi alamat lengkap berdasarkan URL halaman dengan new URL
ValidationException yang menyebut pollingIntervalOpsi per pemanggilan ditulis polling_intervalGunakan snake case per pemanggilan, atau setel pollingInterval di constructor
SyntaxError tentang await atau importFile berjalan sebagai CommonJSGunakan ekstensi .mjs atau setel type paket menjadi module
TypeError: getSetCookie is not a functionNode.js yang lebih lama dari 18.15 atau 19.7Perbarui ke Node.js 22 atau lebih baru
ApiException berisi ERROR_CAPTCHA_UNSOLVABLE dalam hitungan detik, setiap kaliFriendly Captcha menolak sitekey atau origin halaman, atau akunnya tidak mengaktifkan v2 atau endpoint EUPeriksa sitekey, URL halaman, dan apiServer; mencoba ulang tidak akan membantu
ApiException berisi ERROR_CAPTCHA_UNSOLVABLE setelah dua menit atau lebihPemecahan melewati Row Timeout CapSkip sebesar 120 detik, atau menunggu thread kosong melewati Wait Timeout-nya sebesar 250 detikSamakan pool dengan Max. Threads, tambahkan proxy, atau naikkan Row Timeout di pengaturan Friendly Captcha
TimeoutException setelah 300 detikTugas menunggu thread kosong, lalu dipecahkan lebih lama daripada waktu polling SDKSamakan pool dengan Max. Threads, atau berikan timeout yang lebih panjang
NetworkException dengan ECONNREFUSEDCapSkip tidak berjalan, atau host dan port-nya salahJalankan CapSkip, lalu periksa apakah seharusnya memakai mode Local atau mode Server

FAQ

Apakah saya butuh Puppeteer atau Playwright untuk memecahkan Friendly Captcha di Node.js?

Tidak. Skrip Anda hanya butuh HTML, yang diambil oleh fetch. Pekerjaan browser terjadi di dalam CapSkip, yang memecahkan widget v2 di browser miliknya sendiri dan puzzle v1 secara langsung. Jika skrip Anda sudah mengendalikan browser untuk keperluan lain, pemanggilan yang sama tetap berfungsi: baca sitekey dan alamat skrip dari halaman yang sedang terbuka, bukan dari HTML hasil fetch.

Apakah ini berfungsi di TypeScript?

Ya. Paket capskip menyertakan definisi tipenya sendiri, dan objek opsi diberi tipe dengan nama camelCase yang dipakai di sini maupun nama API dalam snake_case. Satu catatan: tipe hasil bersama mendeklarasikan token sebagai opsional dan masih mendeskripsikannya sebagai field ALTCHA, karena satu bentuk hasil dipakai untuk semua metode. friendlyCaptcha selalu mengisinya, jadi non-null assertion pada result.token aman. Metode attr milik cheerio juga mengembalikan string atau undefined, jadi lakukan assertion yang sama pada sitekey dan src skrip dalam strict mode.

Bisakah aplikasi Node di VPS memakai pemecah CAPTCHA di PC Windows saya?

Bisa. Alihkan CapSkip ke mode Server di pengaturan koneksi agar ia mendengarkan di alamat jaringan, bukan di loopback, setel CAPSKIP_HOST di tempat aplikasi berjalan, lalu teruskan ke klien seperti pada contoh lengkap. VPS, container, dan hosted runner semuanya terhubung melalui API HTTP yang sama. Gunakan IP publik statis dengan aturan firewall jika rutenya melewati internet. Pemecah CAPTCHA tetap berada di perangkat milik Anda, jadi menjalankan lebih banyak pemecahan tidak pernah mengubah biaya yang Anda bayar.

Apa bedanya dengan panduan Python?

Endpoint, opsi, dan hasilnya sama di setiap SDK CapSkip; yang berbeda adalah kode di sekitarnya. Di Python, requests.Session menyimpan cookie untuk Anda, sedangkan fetch milik Node membutuhkan jar kecil dari Langkah 1. AsyncCapSkip di Python adalah klien asinkron tersendiri, sedangkan di Node setiap metode sudah asinkron, jadi satu-satunya yang perlu ditambahkan adalah batas jumlah yang berjalan bersamaan. Versi Python dari alur kerja ini ada di panduan Friendly Captcha untuk Python.

Versi singkatnya

Untuk memecahkan Friendly Captcha di Node.js, ambil halamannya dan simpan cookie-nya dengan getSetCookie, lalu baca elemen frc-captcha dan tag script-nya dengan cheerio, sambil mengubah setiap src menjadi alamat lengkap. Tentukan v1 atau v2 dari nama paket atau nama file, atau dari atribut milik widget itu sendiri, dan lempar exception jika keduanya tidak memberi jawaban. Panggil friendlyCaptcha dengan sitekey, URL halaman, versi tersebut, dan alamat skrip, ditambah apiServer untuk situs EU. Kirim token sekali saja, di body, bersama field milik form itu sendiri dan pada field yang namanya ditentukan oleh versinya.

Friendly Captcha menagih tingkat kesulitannya dalam waktu CPU, bukan uang, jadi hari yang sibuk hanya memakan waktu pemecahan di mesin Anda sendiri, tidak lebih. Jalankan pemecah captcha lokal dan satu-satunya batas Anda adalah mesin itu serta thread yang Anda berikan kepadanya.