C# में कैप्चा डिटेक्टर: प्रकार पहचानें, CapSkip को कॉल करें

captcha detector in c# - CAPTCHA Detector in C#: Find the Type, Call CapSkip

C# में कैप्चा डिटेक्टर पेज के HTML में विजेट की script और container पढ़ता है, उनसे sitekey निकालता है, और उस परिवार को उस CapSkip method से जोड़ता है जो उसे हल करता है। reCAPTCHA v2 बॉक्स, v3 script और Turnstile विजेट के लिए अलग-अलग options वाली तीन अलग कॉल चाहिए, और गलत अनुमान से एक पूरा हल बेकार जाता है और ऐसा टोकन मिलता है जिसका आप इस्तेमाल नहीं कर सकते। यह गाइड करीब 200 लाइन का एक डिटेक्टर बनाती है जिसमें CapSkip के अलावा कोई पैकेज नहीं है। वह प्रकार, method और arguments लौटाता है, या साफ़ कह देता है कि वह पहचान नहीं पाया। CapSkip आपके अपने Windows PC पर चलता है और प्रति-हल कोई शुल्क नहीं लगता, इसलिए सौ पेज जाँचने वाले प्रोग्राम पर कोई अतिरिक्त खर्च नहीं आता। यही डिटेक्टर Python और Node.js में भी है, जिनके लिंक आख़िर में दिए हैं।

आपको क्या चाहिए

  • किसी Windows PC पर चल रहा CapSkip, जिसमें आपके ज़रूरी सॉल्वर चालू हों। पहली बार शुरू करने के चरण सेटअप गाइड में समझाए गए हैं।
  • ज़रूरी: .NET 8 SDK या बाद का, क्योंकि कोड raw string literals इस्तेमाल करता है, जिनके लिए C# 11 चाहिए। इसे SDK 10.0.100 और CapSkip पैकेज 1.3.0 के साथ टेस्ट किया गया, net8.0 और net10.0 को टारगेट करते हुए। net6.0 को टारगेट करना भी काम किया, प्रोजेक्ट फ़ाइल में LangVersion 11 जोड़ने के बाद। .NET Framework आज़माया नहीं गया।
  • एक terminal। कमांड PowerShell में काम करती हैं।
  • ऐसा पेज जो आपका अपना हो या जिसे ऑटोमेट करने की आपको अनुमति हो, और जिस पर कैप्चा लगा हो।
  • सॉल्वर के लिए एक पता। Local मोड में CapSkip केवल उसी डिवाइस के लिए 127.0.0.1 पर जवाब देता है। Server मोड में वह आपके नेटवर्क पते या पब्लिक IP पर सुनता है, ताकि किसी दूसरे PC, VPS या होस्टेड प्लेटफ़ॉर्म पर चल रहा प्रोग्राम API के ज़रिए उसे कॉल कर सके। दोनों यहाँ सेट किए जाते हैं: कनेक्शन सेटिंग्स.

यह कैसे टेस्ट किया गया: डिटेक्टर को उन्हीं 66 सहेजे हुए पेजों पर चलाया गया जिन पर Python वाला चला था, और हर पेज पर वही नतीजा मिला। इनमें 59 हाथ से बनाए गए थे (करीब 40 वेंडरों के दस्तावेज़ के अनुसार हैं, बाकी मेरे गढ़े हुए edge cases हैं) और 7 सार्वजनिक पेजों के स्नैपशॉट हैं जो 29 अक्टूबर 2026 को लिए गए। जिन 40 नतीजों में method का नाम था, उन्हें असली CapSkip NuGet पैकेज से एक ऐसे mock CapSkip को भेजा गया जो ऐप की input जाँचों की नकल करता है। असली ऐप नहीं चल रहा था, इसलिए नीचे कोई भी संख्या हल का समय नहीं है।

हल करने से पहले कैप्चा का प्रकार क्यों पहचानें

CapSkip में हर परिवार के लिए अलग method है: SDK में RecaptchaAsync, TurnstileAsync, GeetestAsync, AltchaAsync, CapyAsync, CaptchaFoxAsync, FriendlyCaptchaAsync और इमेज के लिए NormalAsync, और साथ में HTTP API पर hcaptcha, जिसे SDK अभी wrap नहीं करता। reCAPTCHA के अंदर भी options मायने रखते हैं। Invisible, Enterprise और v3 एक ही कॉल पर लगने वाले flags हैं। गलत flags भेजने पर सबसे अच्छी स्थिति में जॉब फेल हो जाती है, और सबसे बुरी में साइट ऐसा टोकन ठुकरा देती है और कारण का कोई संकेत नहीं देती। असली ऐप गलत reCAPTCHA वर्ज़न पर इनमें से क्या करता है, यह मैं जाँच नहीं पाया। दोनों हालत में एक हल का समय बेकार जाता है।

बाकी गलतियाँ तुरंत और साफ़ दिखती हैं। जिस पेज पर reCAPTCHA explicit तरीके से रेंडर होता है, उससे sitekey पढ़ने पर शब्द explicit मिलता है, और CapSkip उसे ERROR_GOOGLEKEY के साथ ठुकरा देता है। ERROR_GOOGLEKEY और ERROR_PAGEURL गाइड इस error को दूसरी तरफ़ से समझाती है। कैप्चा का प्रकार भरोसे के साथ पहचानने के लिए डिटेक्टर को संकेत एक तय क्रम में जाँचने होते हैं, और वही क्रम दोनों तरह की गलतियों से बचाता है। जो प्रोग्राम एक से ज़्यादा साइट से मिलेगा, उससे पहले बनाने लायक पहली चीज़ भी डिटेक्टर ही है। हर प्रकार की पृष्ठभूमि जाननी हो, तो कैप्चा क्या है गाइड बिना कोड के इन परिवारों को समझाती है।

चरण 1: प्रोजेक्ट बनाएँ

एक console प्रोजेक्ट बनाएँ और CapSkip पैकेज जोड़ें। डिटेक्टर खुद सिर्फ़ वही इस्तेमाल करता है जो .NET के साथ आता है: regular expressions और HttpClient।

dotnet new console -o detect-demo
cd detect-demo
dotnet add package CapSkip

C# में कैप्चा डिटेक्टर टेक्स्ट पर काम करता है, इसलिए उसे सिर्फ़ दो इनपुट चाहिए: पेज का HTML और उसका पता। सादा request वह दिखाता है जो सर्वर किसी भी JavaScript के चलने से पहले भेजता है। Google के reCAPTCHA डेमो पेज, Cloudflare का Turnstile डेमो और hCaptcha डेमो, सब अपना विजेट उसी HTML में रखते हैं। जो पेज विजेट JavaScript से जोड़ते हैं, उनमें ऐसा नहीं होता, और “जब HTML कुछ नहीं बताता” वाला सेक्शन उसी स्थिति को कवर करता है। एक HttpClient एक cookie jar रखता है, जो इमेज कैप्चा वाले चरण के लिए मायने रखता है: नीचे का प्रोग्राम पेज और तस्वीर, दोनों के लिए वही client इस्तेमाल करता है।

चरण 2: सहायक फ़ंक्शन लिखें

यहाँ से चरण 9 तक सब कुछ Detector.cs नाम की एक ही फ़ाइल में सेव करें, एक के बाद एक ब्लॉक। चरण 9 में class बंद होने तक फ़ाइल compile नहीं होगी, और वह उन implicit usings पर निर्भर है जिन्हें dotnet new console चालू करता है। पहले ब्लॉक में usings, दो छोटे records और पहले helpers हैं। कुछ न मिलने पर हर reader null लौटाता है, इसलिए छूटा हुआ attribute कभी exception नहीं फेंकता।

using System.Net;
using System.Text.Json;
using System.Text.RegularExpressions;
using CapSkip;

public record Info(string Type, string? Method, Dictionary<string, object?> Args,
    string? NeedsBrowser = null, bool Unsupported = false, string[]? Also = null);
record Page(string H, string Srcs, string Js, string Url);

public static class Detector
{
    const RegexOptions F = RegexOptions.IgnoreCase | RegexOptions.Singleline | RegexOptions.CultureInvariant;
    const string SiteKeyJs = """sitekey['"]?\s*:\s*['"]([^'"]+)""";

    static string? Grab(string pattern, string? text)  // first capture group that matched, or null
    {
        var m = Regex.Match(text ?? "", pattern, F);
        for (var i = 1; i < m.Groups.Count; i++) if (m.Groups[i].Success) return m.Groups[i].Value;
        return null;
    }
    static string? Or(params string?[] v) => v.FirstOrDefault(s => !string.IsNullOrEmpty(s));
    static string Abs(string url, string rel) => Uri.TryCreate(new Uri(url), rel, out var u) ? u.AbsoluteUri : rel;
    static Dictionary<string, object?> Args(params (string, object?)[] kv) =>  // null values are left out
        kv.Where(x => x.Item2 != null).ToDictionary(x => x.Item1, x => x.Item2);

Grab method पहला capture group लौटाता है जो मैच हुआ। Tag method CSS class से opening tag ढूँढता है और ऐसी class को ठुकरा देता है जिसके लंबे नाम के अंदर बस वह नाम आता हो, इसलिए cf-turnstile-response नाम की class Turnstile विजेट नहीं है। Attr method दोनों तरह के quotes में लिखा attribute पढ़ता है और HTML entities को decode करता है, जो attribute में रखे challenge के लिए मायने रखता है। बिना quotes वाले attributes नहीं पढ़े जाते।

दूसरा ब्लॉक पेज को ऐसी चीज़ में बदलता है जिसे सारी जाँचें साझा कर सकें। वह comments और style ब्लॉक हटा देता है, ताकि विजेट की class का ज़िक्र करने वाला CSS नियम मैच न हो सके, और script के पतों तथा inline JavaScript को अलग-अलग इकट्ठा करता है। आख़िरी helper उन पेजों का जवाब बनाता है जिनके लिए HTML काफ़ी नहीं है। उसके NeedsBrowser फ़ील्ड में वजह रहती है, जैसे यह कि sitekey JavaScript से सेट होती है, और यह पोस्ट इसे ब्राउज़र चाहिए वाला नतीजा कहती है।

    static string? Attr(string? tag, string name) =>  // attribute value, either quote style, HTML-decoded
        Grab("""\s""" + name + """\s*=\s*(?:"([^"]*)"|'([^']*)')""", tag) is { } v ? WebUtility.HtmlDecode(v) : null;
    static string? Tag(string cls, string h) =>  // opening tag that has CSS class cls
        Grab("""(<\w+[^>]*\sclass\s*=\s*["'][^"']*(?<![\w-])""" + cls + """(?![\w-])[^"']*["'][^>]*>)""", h);
    static string? Opts(string call, string js) => Grab(call + """\([^,]+,\s*\{(.{0,400})""", js);

    static Page Parse(string html, string url)
    {
        var h = Regex.Replace(html, """<!--.*?-->|<style\b.*?</style>""", "", F);
        var srcs = Regex.Matches(h, """<script\b[^>]*?\ssrc\s*=\s*["']([^"']+)""", RegexOptions.IgnoreCase);
        var js = Regex.Matches(h, """<script\b[^>]*>(.*?)</script>""", F);
        return new(h, WebUtility.HtmlDecode(string.Join(" ", srcs.Select(m => m.Groups[1].Value))),
                   string.Join("\n", js.Select(m => m.Groups[1].Value)), url);
    }
    static Info Browser(string type, string method, Page p, string why) =>  // HTML is not enough: do not guess
        new(type, method, new() { ["url"] = p.Url }, why);
    static readonly (string, string)[] Vendors = { ("arkose", """arkoselabs\.com/v2/|funcaptcha\.com/fc/"""),
        ("geetest_v4", """gt4\.js|initGeetest4""") };
    static Info? Unsup(Page p) => Vendors.Where(v => Regex.IsMatch(p.Srcs + " " + p.Js, v.Item2))  // CapSkip cannot solve these
        .Select(v => new Info(v.Item1, null, new(), Unsupported: true)).FirstOrDefault();
    static Info? Cf(Page p) => Regex.IsMatch(p.Js, """_cf_chl_opt\s*=\s*\{""")  // the "Just a moment..." page, not a widget
        ? Browser("turnstile_challenge", "turnstile", p, "data and pagedata (Cloudflare calls them cData and chlPageData) only exist at run time") : null;
    static Info? Hcaptcha(Page p)  // before reCAPTCHA: a site that moved to hCaptcha may keep the g-recaptcha class
    {
        if (!Regex.IsMatch(p.Srcs + " " + p.Js, """hcaptcha\.com/1/api\.js""")) return null;
        if (Regex.IsMatch(p.Js, """\.setData\s*\(|rqdata"""))  // Enterprise rqdata is issued per challenge
            return Browser("hcaptcha_enterprise", "hcaptcha", p, "rqdata is issued for one challenge at run time");
        var t = Tag("h-captcha", p.H) ?? Tag("g-recaptcha", p.H);
        var key = Or(Attr(t, "data-sitekey"), Grab(SiteKeyJs, Opts("""hcaptcha\.render""", p.Js)));
        if (key == null) return Browser("hcaptcha", "hcaptcha", p, "no sitekey in the static HTML");
        var invisible = Attr(t, "data-size") == "invisible" ? 1 : (int?)null;
        return new("hcaptcha", "hcaptcha", Args(("sitekey", key), ("url", p.Url), ("invisible", invisible)));
    }

चरण 3: hCaptcha, और जो CapSkip हल नहीं कर सकता

ये तीनों जाँचें ऊपर के दूसरे ब्लॉक के अंत में हैं, इसलिए इस चरण में पेस्ट करने के लिए कुछ नया नहीं है। उन पेजों से शुरू करें जिनका सही जवाब ना है। CapSkip FunCaptcha या Arkose को हल नहीं करता, और GeeTest v4 भी GeeTest method के दायरे से बाहर है, जो v3 के लिए है। अगर डिटेक्टर इन्हें छोड़ दे, तो Arkose बॉक्स वाला पेज अगली जाँच में चला जाएगा और ऐसी चीज़ के रूप में लौट सकता है जो वह है नहीं। इसलिए यह जाँच सबसे पहले चलती है और बिना method वाला नतीजा लौटाती है।

यही ब्लॉक Cloudflare की पूरे पेज वाली जाँच, यानी “Just a moment” वाले बीच के पेज, को भी संभालता है। वह पेज Turnstile विजेट नहीं है। turnstile method को उससे दो मान चाहिए, data और pagedata, और सादा request उन्हें नहीं रखता। डिटेक्टर पेज के inline challenge options खोजता है और जवाब देता है कि ब्राउज़र चाहिए। cf_clearance गाइड बताती है कि challenge पेज पार हो जाने के बाद आगे क्या होता है।

इसके बाद, reCAPTCHA से पहले, hCaptcha आता है। CapSkip इसे हल करता है, चाहे विजेट checkbox हो, invisible हो या Enterprise, लेकिन जो साइट reCAPTCHA से hCaptcha पर गई है वह अक्सर अपने विजेट पर g-recaptcha class रहने देती है, और सिर्फ़ hCaptcha की script ही दोनों में फ़र्क बताती है। यह जाँच sitekey को विजेट से या hcaptcha.render कॉल से पढ़ती है, और जब विजेट पर data-size का मान invisible हो तो invisible जोड़ देती है। जो Enterprise पेज hcaptcha.setData के ज़रिए विजेट को rqdata मान देता है, उसे इसके बजाय ब्राउज़र की ज़रूरत वाला नतीजा मिलता है, क्योंकि वह मान एक ही challenge के लिए जारी होता है और जल्दी expire हो जाता है।

चरण 4: reCAPTCHA, सही क्रम में

reCAPTCHA में सबसे ज़्यादा सावधानी चाहिए, क्योंकि कई CapSkip variants दो script नामों को साझा करते हैं। सवालों का क्रम ही उन्हें अलग रखता है। पहले script का नाम: enterprise.js का मतलब Enterprise flag है, api.js का नहीं। फिर script के पते में render parameter। render= के बाद की key का मतलब v3 है, लेकिन render= के बाद शब्द explicit का मतलब है कि यह v2 पेज अपना विजेट कोड से रेंडर करता है, और उसे key की तरह कभी इस्तेमाल नहीं करना चाहिए। फिर markup। g-recaptcha class और data-action attribute वाला button Google की v3 button binding है। data-action के बिना वाला button, या data-size को invisible पर सेट करने वाला कोई भी element, invisible v2 विजेट है। उस class वाली बाकी हर चीज़ checkbox है।

    static Info? Recaptcha(Page p)  // Enterprise flag, then v3 (render=KEY), then v2
    {
        var m = Regex.Match(p.Srcs, """/recaptcha/(api|enterprise)\.js(\?\S*)?""");
        if (!m.Success) return null;
        var render = Grab("""[?&]render=([^&\s]+)""", m.Groups[2].Value);  // a v3 key, or "explicit" (a v2 page)
        var t = Tag("g-recaptcha", p.H);
        var o = Opts("""\.render""", p.Js);
        var button = (t ?? "").StartsWith("<button", StringComparison.OrdinalIgnoreCase);
        var action = Attr(t, "data-action");
        var v3 = (render != null && render != "explicit") || (button && !string.IsNullOrEmpty(action));
        var key = Or(render == "explicit" ? null : render, Attr(t, "data-sitekey"), Grab(SiteKeyJs, o));
        if (key == null) return Browser("recaptcha", "recaptcha", p, "no sitekey in the static HTML (set by JavaScript)");
        var small = Attr(t, "data-size") == "invisible" || Grab("""size['"]?\s*:\s*['"](invisible)""", o) != null;
        var invisible = !v3 && (button || small);  // a <button> is invisible v2 unless it has data-action (v3)
        action = Or(action, Grab("""execute\(\s*[^,)]+,\s*\{\s*['"]?action['"]?\s*:\s*['"]([^'"]+)""", p.Js));
        var ent = m.Groups[1].Value == "enterprise";
        var a = Args(("sitekey", key), ("url", p.Url), ("enterprise", ent ? 1 : null), ("version", v3 ? "v3" : null),
                     ("action", v3 ? action : null), ("invisible", invisible ? 1 : null));
        return new("recaptcha_" + (v3 ? "v3" : invisible ? "v2_invisible" : "v2") + (ent ? "_enterprise" : ""), "recaptcha", a);
    }

हर नतीजा एक RecaptchaAsync कॉल से जुड़ता है। Checkbox यानी sitekey, पेज URL और एक वैकल्पिक options dictionary के साथ RecaptchaAsync। Invisible "invisible" जोड़ता है, Enterprise "enterprise" जोड़ता है, और v3 "version" और action जोड़ता है। action का नाम data-action attribute से या पेज की execute कॉल से आता है; अगर HTML में दोनों में से कोई न हो, तो वह entry छोड़ दी जाती है और CapSkip अपना default verify इस्तेमाल करता है। इसके हर variant की जानकारी reCAPTCHA सॉल्वर पेज पर है।

चरण 5: Turnstile और GeeTest

Turnstile विजेट को पेज पर दो चीज़ें चाहिए: Cloudflare की api.js और एक sitekey। key cf-turnstile class वाले div के data-sitekey attribute में होती है, या turnstile.render कॉल में। जाँच के लिए script का होना ज़रूरी है, इसलिए किसी असंबंधित पेज पर भटकी हुई class का नाम इसे चालू नहीं कर सकता। यह data-action और data-cdata भी मौजूद होने पर पढ़ती है और उन्हें action और data के रूप में आगे पास कर देती है। options की सूची Turnstile सॉल्वर पेज पर मिलती है।

GeeTest v3 आपको दो मान देता है, और उनमें से एक जाल है। gt मान साइट के लिए तय रहता है और पेज में हो सकता है। challenge मान एक ही बार चलता है, और साइटें उसे run time पर एक register endpoint से लाती हैं। अगर पेज में दोनों हों, तो डिटेक्टर उन्हें लौटा देता है। अगर उसमें सिर्फ़ initGeetest कॉल हो, तो वह जवाब देता है कि HTML काफ़ी नहीं है, क्योंकि सहेजे हुए पेज से कॉपी किया गया challenge पहले ही बेकार हो चुका होगा।

    static Info? Turnstile(Page p)  // widget only: div.cf-turnstile or turnstile.render
    {
        if (!p.Srcs.Contains("challenges.cloudflare.com/turnstile/v0/api.js")) return null;
        var t = Tag("cf-turnstile", p.H);
        var o = Opts("""turnstile\.render""", p.Js);
        var key = Or(Attr(t, "data-sitekey"), Grab(SiteKeyJs, o));
        if (key == null) return Browser("turnstile", "turnstile", p, "no sitekey in the static HTML");
        var action = Or(Attr(t, "data-action"), Grab("""\baction['"]?\s*:\s*['"]([^'"]+)""", o));
        var cdata = Or(Attr(t, "data-cdata"), Grab("""\bcData['"]?\s*:\s*['"]([^'"]+)""", o));
        return new("turnstile", "turnstile", Args(("sitekey", key), ("url", p.Url), ("action", action), ("data", cdata)));
    }
    static Info? Geetest(Page p)  // v3 only: gt is static, challenge comes from the register endpoint
    {
        if (!Regex.IsMatch(p.Js, """initGeetest\s*\(""") && !Regex.IsMatch(p.Srcs, """/gt(\.[\d.]+)?\.js""")) return null;
        var gt = Grab("""\bgt['"]?\s*:\s*['"]([0-9a-f]{32})""", p.Js);
        var ch = Grab("""\bchallenge['"]?\s*:\s*['"]([0-9a-f]{32,34})""", p.Js);
        return gt == null || ch == null ? Browser("geetest", "geetest", p, "gt and challenge come from the register endpoint")
            : new("geetest", "geetest", Args(("gt", gt), ("challenge", ch), ("url", p.Url)));
    }

चरण 6: ALTCHA और Capy

ALTCHA ने अपनी पीढ़ियों के बीच attribute के नाम बदले। वर्ज़न 1 और 2 challengeurl इस्तेमाल करते हैं, या inline challenge के लिए challengejson। वर्ज़न 3 और बाद के एक ही attribute challenge इस्तेमाल करते हैं, जो URL या challenge data, दोनों में से कुछ भी ले सकता है। जाँच तीनों पढ़ती है और relative URL को पेज के पते से जोड़ देती है। C# में option keys challenge_url और challenge_json हैं।

इस जाँच के लिए Capy को विजेट div नहीं चाहिए। उसकी key PUZZLE_ से शुरू होती है और विजेट script के पते के k parameter में रहती है, और https://jp.api.capy.me के अलावा कोई host api_server के रूप में पास किया जाता है।

    static Info? Altcha(Page p)  // challengeurl / challengejson (v1, v2) or challenge (v3+)
    {
        var t = Grab("""(<altcha-widget\b[^>]*>)""", p.H);
        if (t == null) return null;
        var c = Attr(t, "challenge");
        var json = Or(Attr(t, "challengejson"), (c ?? "").TrimStart().StartsWith('{') ? c : null);
        var url = Or(Attr(t, "challengeurl"), json != null ? null : c);
        return json != null ? new("altcha", "altcha", Args(("url", p.Url), ("challenge_json", json)))
            : url != null ? new("altcha", "altcha", Args(("url", p.Url), ("challenge_url", Abs(p.Url, url))))
            : Browser("altcha", "altcha", p, "no challenge attribute");
    }
    static Info? Capy(Page p)  // the widget script is .../puzzle/get_js/?k=PUZZLE_...
    {
        var m = Regex.Match(p.Srcs, """((?:https?:)?//[^\s"']+?)/puzzle/get_js/\S*?(PUZZLE_[\w-]+)""");
        if (!m.Success) return null;
        var full = m.Groups[1].Value.StartsWith("http") ? m.Groups[1].Value : "https:" + m.Groups[1].Value;
        var host = full == "https://jp.api.capy.me" ? null : full;
        return new("capy", "capy", Args(("sitekey", m.Groups[2].Value), ("url", p.Url), ("api_server", host)));
    }

चरण 7: CaptchaFox और Friendly Captcha

CaptchaFox विजेट पेजों तक दो जगहों से पहुँचता है। सादा वाला cdn.captchafox.com से लोड होता है। दूसरा किसी होस्टिंग प्लेटफ़ॉर्म द्वारा साझा CDN पाथ के नीचे bundle किया जाता है, इसलिए डिटेक्टर उस पाथ में CaptchaFox पैकेज का नाम खोजता है, अकेले पाथ को नहीं, जिसे दूसरे विजेट भी इस्तेमाल करते हैं।

Friendly Captcha सबसे पेचीदा है। वर्ज़न 1 और वर्ज़न 2 एक ही div और एक ही sitekey namespace इस्तेमाल करते हैं, लेकिन protocol अलग हैं, और CapSkip गलत वर्ज़न के लिए भी सही बनावट का टोकन लौटा देता है, जिसे साइट ठुकरा देती है। script के नाम दोनों को अलग बताते हैं: वर्ज़न 2 @friendlycaptcha/sdk पैकेज लोड करता है, वर्ज़न 1 friendly-challenge। जब पेज कोई संकेत न दे, तो डिटेक्टर अनुमान लगाने से मना कर देता है और कहता है कि HTML काफ़ी नहीं है।

    const string Mam = "mampkg/@mamdev/core.frontend.libs.captchafox";
    static Info? CaptchaFox(Page p)  // plain build from cdn.captchafox.com, or the bundled build from s.uicdn.com
    {
        var t = Tag("captchafox", p.H);
        var key = Grab("""captchafox[^"']*/api\.js\?key=([^"'&]+)""", p.Srcs);
        if (t == null && key == null) return null;
        var mam = p.Srcs.Contains(Mam) ? "https://s.uicdn.com/" + Mam + "/" : null;
        var sitekey = Or(Attr(t, "data-sitekey"), key);
        if (sitekey == null) return Browser("captchafox", "captchafox", p, "no sitekey in the static HTML");
        return new("captchafox", "captchafox", Args(("sitekey", sitekey), ("url", p.Url), ("api_server", mam)));
    }
    static Info? Friendly(Page p)  // v2 loads @friendlycaptcha/sdk (site.min.js), v1 loads friendly-challenge
    {
        var t = Tag("frc-captcha", p.H);
        if (t == null) return null;
        var e2 = Attr(t, "data-api-endpoint");
        var e1 = Attr(t, "data-puzzle-endpoint");
        if (Attr(t, "data-sitekey") == null) return Browser("friendly_captcha", "friendly_captcha", p, "no sitekey in the static HTML");
        var v2 = e2 != null || Regex.IsMatch(p.Srcs, """friendlycaptcha/sdk|site(\.compat)?\.min\.js""");
        var v1 = e1 != null || Regex.IsMatch(p.Srcs, """friendly-challenge|widget(\.module)?\.min\.js""");
        if (v2 == v1) return Browser("friendly_captcha", "friendly_captcha", p, "cannot tell v1 from v2 in the HTML");
        return new(v2 ? "friendly_captcha_v2" : "friendly_captcha_v1", "friendly_captcha", Args(("sitekey", Attr(t, "data-sitekey")),
            ("url", p.Url), ("version", v2 ? "v2" : "v1"), ("api_server", Or(e2, e1))));
    }

इनमें से हर एक का अपना सॉल्वर पेज है, जिसमें उस method के options दिए हैं:

चरण 8: इमेज कैप्चा, एक अनुमान, और जाँचें

इमेज कैप्चा में खोजने के लिए कोई vendor script नहीं होती, इसलिए यह जाँच एक अनुमान है और तभी चलती है जब और कुछ मैच न हुआ हो। जाँच को ऐसी इमेज चाहिए जिसके tag में captcha लिखा हो, और पेज में कहीं ऐसा टेक्स्ट बॉक्स जिसके tag में captcha या security code लिखा हो। हर उस मामले में यह चूक गई जहाँ markup में ये शब्द नहीं थे, और बनाया हुआ canvas इसे दिखता ही नहीं। hit को संभावित मानें, पक्का नहीं।

वही ब्लॉक जाँचों को क्रम से सूचीबद्ध करता है और Detect को उपलब्ध कराता है। यही क्रम तर्क है: पहले unsupported vendors, फिर Cloudflare पेज, फिर hCaptcha, फिर बाकी हर परिवार, और इमेज का अनुमान तभी जब सूची को कुछ न मिला हो। Detect method पहला नतीजा लौटाता है और जब पेज में एक से ज़्यादा परिवार हों, जैसे v3 script के साथ Turnstile बॉक्स, तो Also सूची जोड़ देता है। सूची परिवारों के नाम बताती है, किसी दूसरी key वाला दूसरा reCAPTCHA विजेट नहीं।

    static Info? Image(Page p)  // a guess: an <img> that says captcha, next to a text box that says captcha
    {
        const string box = """<input\b(?![^>]*type\s*=\s*["']?(?:hidden|checkbox|submit))[^>]*(?:captcha|security.?code)""";
        if (!Regex.IsMatch(p.H, box, RegexOptions.IgnoreCase)) return null;
        foreach (Match m in Regex.Matches(p.H, """<img\b[^>]*>""", RegexOptions.IgnoreCase))
            if (Regex.IsMatch(m.Value, "captcha", RegexOptions.IgnoreCase) && Attr(m.Value, "src") is { Length: > 0 } src)
                return new("image", "normal", Args(("image", Abs(p.Url, src))));
        return null;
    }
    public static Info? Detect(string html, string pageUrl)  // every check runs; the first hit wins, the rest are "also"
    {
        var p = Parse(html, pageUrl);
        var found = new[] { Unsup(p), Cf(p), Hcaptcha(p), Recaptcha(p), Turnstile(p), Geetest(p), Altcha(p), Capy(p), CaptchaFox(p), Friendly(p) }
            .Where(r => r != null).ToList();
        return found.Count == 0 ? Image(p) : found.Count == 1 ? found[0]
            : found[0]! with { Also = found.Skip(1).Select(r => r!.Type).ToArray() };
    }

चरण 9: solve

आख़िरी ब्लॉक नतीजे को सही CapSkip method तक भेजता है। unsupported या ब्राउज़र चाहिए चिह्नित किसी भी नतीजे को वह InvalidOperationException के साथ मना कर देता है। CapSkip methods अपने मुख्य मान position से लेते हैं और बाकी options dictionary के रूप में, इसलिए ब्लॉक arguments से positional मान निकालता है और बाकी सब कच्चे API नामों के तहत आगे पास कर देता है। इमेज आपके अपने HttpClient से डाउनलोड होती है, इसलिए उसकी cookie jar request के साथ जाती है, क्योंकि SDK का अपना URL डाउनलोड कोई cookie नहीं भेजता। उस डाउनलोड पर खराब status HttpRequestException फेंकता है।

SDK में अभी कोई hCaptcha कॉल नहीं है, इसलिए hCaptcha नतीजा इसके बजाय SolveHcaptcha को जाता है। यह sitekey और पेज का पता method=hcaptcha के साथ in.php पर पोस्ट करता है, फिर token तैयार होने तक हर 5 सेकंड में res.php को poll करता है, जैसा कि API दस्तावेज़ों का hCaptcha सेक्शन बताता है। यह पता और API key उस CapSkipClient से पढ़ता है जिसे आप पास करते हैं, इसलिए अलग से कुछ कॉन्फ़िगर नहीं करना पड़ता। ठुकराई गई जॉब error code के साथ SDK का अपना ApiException throw करती है, इसलिए नीचे के प्रोग्राम में CapSkipError वाला catch उसे संभाल लेता है। नतीजे में token, Code में होता है और जिस User-Agent से वह मिला, वह UserAgent में।

    public static async Task<SolveResult> Solve(Info i, CapSkipClient s, HttpClient http)
    {
        if (i.Unsupported || i.NeedsBrowser != null) throw new InvalidOperationException(i.NeedsBrowser ?? i.Type + " is not supported by CapSkip");
        if (i.Method == "hcaptcha") return await SolveHcaptcha(i.Args, s, http);
        string K(string k) => (string)i.Args[k]!;  // the positional arguments
        var o = i.Args.Where(kv => kv.Key is not ("sitekey" or "url" or "gt" or "challenge" or "image"))
                      .ToDictionary(kv => kv.Key, kv => kv.Value);  // everything else is an options entry
        return i.Method switch
        {
            "recaptcha" => await s.RecaptchaAsync(K("sitekey"), K("url"), o),
            "turnstile" => await s.TurnstileAsync(K("sitekey"), K("url"), o),
            "geetest" => await s.GeetestAsync(K("gt"), K("challenge"), K("url"), o),
            "altcha" => await s.AltchaAsync(K("url"), o),
            "capy" => await s.CapyAsync(K("sitekey"), K("url"), o),
            "captchafox" => await s.CaptchaFoxAsync(K("sitekey"), K("url"), o),
            "friendly_captcha" => await s.FriendlyCaptchaAsync(K("sitekey"), K("url"), o),
            "normal" => await s.NormalAsync(await ImageData(K("image"), http)),  // fetched with YOUR cookies
            _ => throw new InvalidOperationException("unknown method " + i.Method),
        };
    }
    static async Task<string> ImageData(string src, HttpClient http) => src.StartsWith("data:") ? src
        : "data:image/png;base64," + Convert.ToBase64String(await http.GetByteArrayAsync(src));
    static async Task<SolveResult> SolveHcaptcha(Dictionary<string, object?> a, CapSkipClient s, HttpClient http)
    {   // no SDK call yet: post to in.php and poll res.php at the address the client already uses
        var form = new Dictionary<string, string> { ["method"] = "hcaptcha", ["sitekey"] = (string)a["sitekey"]!,
            ["pageurl"] = (string)a["url"]!, ["invisible"] = a.ContainsKey("invisible") ? "1" : "0", ["json"] = "1" };
        if (a.TryGetValue("data", out var d) && d != null) form["data"] = d.ToString()!;
        var key = string.IsNullOrEmpty(s.ApiKey) ? "" : "&key=" + Uri.EscapeDataString(s.ApiKey);
        if (key != "") form["key"] = s.ApiKey;
        using var sent = await http.PostAsync(s.ApiClient.BaseUrl + "/in.php", new FormUrlEncodedContent(form));
        var job = JsonDocument.Parse(await sent.Content.ReadAsStringAsync()).RootElement;
        var id = job.GetProperty("request").GetString()!;
        if (job.GetProperty("status").GetInt32() != 1) throw new ApiException(id);
        for (var n = 0; n < 36; n++)  // a solve takes 10 to 30 seconds, so give up after 3 minutes
        {
            await Task.Delay(5000);
            var r = JsonDocument.Parse(await http.GetStringAsync($"{s.ApiClient.BaseUrl}/res.php?action=get&id={id}&json=1{key}")).RootElement;
            var value = r.GetProperty("request").GetString()!;
            if (value == "CAPCHA_NOT_READY") continue;
            if (r.GetProperty("status").GetInt32() != 1) throw new ApiException(value);
            return new SolveResult { CaptchaId = id, Code = value, UserAgent = r.TryGetProperty("useragent", out var ua) ? ua.GetString() : null };
        }
        throw new CapSkip.TimeoutException("hCaptcha was not solved within 3 minutes");
    }
}

इमेज हल हो जाने पर, जवाब उसी http object से फ़ॉर्म पोस्ट के रूप में वापस भेजें, ताकि साइट वह cookie देख सके जो उसने तस्वीर दिखाते समय सेट की थी। Turnstile या hCaptcha हल के नतीजे में एक UserAgent भी होता है, जिसे challenge पेज पर टोकन के साथ जाना पड़ता है। नीचे का प्रोग्राम सिर्फ़ Code फ़ील्ड प्रिंट करता है, इसलिए GeeTest या Capy का नतीजा कच्चे JSON के रूप में दिखता है।

C# में कैप्चा डिटेक्टर चलाएँ

Program.cs की सामग्री को इस ब्लॉक से बदलें। यह आपका पेज लाता है, HTTP status प्रिंट करता है, डिटेक्टर को जो मिला वह प्रिंट करता है, और CapSkip को तभी कॉल करता है जब कॉल करने के लिए कोई method हो और कुछ छूटा न हो। यह CapSkipError पकड़ता है, जो SDK के exceptions का base है, उन्हें एक-एक करके नाम लेने की जगह। इससे एक जाल भी बचता है: implicit usings चालू होने पर, अकेला TimeoutException System और CapSkip के बीच अस्पष्ट है और compile नहीं होता। इसे dotnet run, दो hyphens और पूरे पेज के पते, https:// समेत, के साथ चलाएँ, जैसा नीचे की कमांड में है। पोर्ट 8080 default है; अगर आपका अलग हो तो CapSkip की कनेक्शन सेटिंग्स में दिखा पोर्ट इस्तेमाल करें।

dotnet run -- "https://example.com/login"

पूरा प्रोग्राम यह है:

using CapSkip;

var pageUrl = args.Length > 0 ? args[0] : "https://example.com/login";
var http = new HttpClient();  // one client keeps one cookie jar for the page and the image
http.DefaultRequestHeaders.UserAgent.ParseAdd("Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/130.0 Safari/537.36");
Info? info;
try
{
    var page = await http.GetAsync(pageUrl);
    Console.WriteLine((int)page.StatusCode);  // a Cloudflare challenge page arrives as a 403
    var finalUrl = page.RequestMessage!.RequestUri!.ToString();  // the URL after redirects
    info = Detector.Detect(await page.Content.ReadAsStringAsync(), finalUrl);
}
catch (Exception e) when (e is HttpRequestException or InvalidOperationException or TaskCanceledException or NotSupportedException)
{
    Console.WriteLine("Could not fetch the page: " + e.Message);
    return;
}
if (info is null) { Console.WriteLine("No CAPTCHA found."); return; }
Console.WriteLine($"{info.Type} via {info.Method ?? "nothing"}"
    + (info.Also is null ? "" : " (also " + string.Join(", ", info.Also) + ")"));
try
{
    var solver = new CapSkipClient(host: "127.0.0.1", port: 8080);
    var result = await Detector.Solve(info, solver, http);
    Console.WriteLine(result.Code.Length > 80 ? result.Code[..80] + "..." : result.Code);
}
catch (InvalidOperationException e) { Console.WriteLine("Not solvable from this HTML: " + e.Message); }
catch (HttpRequestException e) { Console.WriteLine("Image download failed: " + e.Message); }
catch (CapSkipError e) { Console.WriteLine($"{e.GetType().Name}: {e.Message}"); }

reCAPTCHA v2 checkbox वाले पेज पर पहली लाइन प्रकार और method दिखाती है, और दूसरी टोकन। mock वाले रन में यह ऐसा दिखा:

पेजप्रोग्राम यह प्रिंट करता है
reCAPTCHA v2 checkbox200, फिर recaptcha के ज़रिए recaptcha_v2, फिर टोकन
hCaptcha विजेट200, फिर hcaptcha (hcaptcha के ज़रिए), फिर token
Cloudflare “Just a moment” पेज403, फिर turnstile के ज़रिए turnstile_challenge, फिर संदेश कि data और pagedata सिर्फ़ run time पर मौजूद होते हैं
कैप्चा के बिना पेज200, फिर No CAPTCHA found.
404 लौटाने वाला पता404, फिर No CAPTCHA found.

जब HTML कुछ नहीं बताता: ब्राउज़र कंसोल

कुछ पेज लोड होने के बाद JavaScript से अपना विजेट जोड़ते हैं। तब सादा request को कैप्चा मिलता ही नहीं, और डिटेक्टर ऐसे पेज के लिए भी null लौटाता है जिस पर कैप्चा है। दो मामले जाने-पहचाने और अपेक्षित हैं: कोड से जोड़ी गई Google या Cloudflare script, और कोड से सेट की गई sitekey। इनके लिए पेज को Chrome में खोलें, F12 दबाएँ और यह Console में पेस्ट करें। अगर Chrome पेस्ट रोक दे, तो पहले allow pasting टाइप करें और Enter दबाएँ। यह उन resources से sitekeys पढ़ता है जिन्हें पेज पहले ही लोड कर चुका है, इसलिए यह वही देखता है जो ब्राउज़र ने बनाया।

(() => {
  const iframes = [...document.querySelectorAll('iframe')].map((f) => f.src);
  const urls = performance.getEntriesByType('resource').map((r) => r.name).concat(iframes);
  const found = new Map(), add = (o) => found.set(JSON.stringify(o), o);
  for (const u of urls) {
    let m;
    if ((m = u.match(/recaptcha\/(?:api2|(enterprise))\/anchor\?.*?\bk=([\w-]+)/)))
      add({ type: 'recaptcha', sitekey: m[2], enterprise: !!m[1], invisible: /size=invisible/.test(u) });
    else if ((m = u.match(/recaptcha\/(?:api|enterprise)\.js\?.*?\brender=([\w-]+)/)) && m[1] !== 'explicit')
      add({ type: 'recaptcha v3', sitekey: m[1], enterprise: /enterprise\.js/.test(u) });
    else if ((m = u.match(/challenges\.cloudflare\.com\/.*turnstile\/.*\/(0x4[\w-]+|[123]x0+[A-Za-z]+)\//)))
      add({ type: 'turnstile', sitekey: m[1] });
    else if ((m = u.match(/hcaptcha\.com\/captcha\/.*[#&?]sitekey=([\w-]+)/))) add({ type: 'hcaptcha', sitekey: m[1] });
    else if (/arkoselabs\.com\/fc\//.test(u)) add({ type: 'arkose: CapSkip cannot solve' });
  }
  if (window._cf_chl_opt) add({ type: 'cloudflare challenge page' });
  document.querySelectorAll('altcha-widget').forEach((e) => add({ type: 'altcha', challenge: e.getAttribute('challenge') || e.getAttribute('challengeurl') }));
  document.querySelectorAll('.h-captcha[data-sitekey]').forEach((e) => add({ type: 'hcaptcha', sitekey: e.dataset.sitekey }));
  document.querySelectorAll('.frc-captcha, .captchafox').forEach((e) => add({ type: e.classList.contains('captchafox') ? 'captchafox' : 'frc-captcha', sitekey: e.dataset.sitekey }));
  const v3 = [...found.values()].filter((o) => o.type === 'recaptcha v3').map((o) => o.sitekey);  // a v3 badge also adds an invisible anchor frame
  const res = [...found.values()].filter((o) => !(o.type === 'recaptcha' && v3.includes(o.sitekey)));
  return res.length ? res : 'no known CAPTCHA found (GeeTest, Capy and CaptchaFox scripts are not checked)';
})();

इसे reCAPTCHA v2 पेज, एक invisible पेज, एक v3 पेज, एक Turnstile पेज और hCaptcha demo पर sitekey और flags मिल गए, और बिना कैप्चा वाले पेज पर इसने एक संदेश प्रिंट किया। तीन सीमाएँ हैं: ब्राउज़र default रूप से सिर्फ़ करीब 250 resource entries रखता है, इसलिए लंबे समय से चल रहा single page app शुरुआती entries खो सकता है, और snippet Friendly Captcha वर्ज़न 1 और वर्ज़न 2 में फ़र्क नहीं कर सकता, और वह GeeTest, Capy या CaptchaFox की scripts नहीं खोजता। Cloudflare की पूरे पेज वाली जाँच पर यह resource के पते से sitekey दिखा सकता है, लेकिन data और pagedata मान नहीं, जिनके लिए render कॉल पर hook चाहिए। वह hook यहाँ टेस्ट नहीं किया गया।

जहाँ पहचान विफल होती है

स्थितिDetect क्या लौटाता हैक्या करें
विजेट script JavaScript से जोड़ी गईnullपेज को ब्राउज़र में लोड करें और console snippet चलाएँ
sitekey JavaScript से सेट की गईऐसा नतीजा जिसे ब्राउज़र चाहिएkey लाइव पेज से पढ़ें
Cloudflare की पूरे पेज वाली जाँचऐसा नतीजा जिसे ब्राउज़र चाहिएमान ब्राउज़र में पढ़ें, cf_clearance गाइड देखें
HTML में frc-captcha div के बिना Friendly Captcha scriptnullconsole snippet चलाएँ या पेज का source पढ़ें
JavaScript से जोड़ी गई CaptchaFox या Capy scriptnullconsole snippet चलाएँ या पेज का source पढ़ें
साइट की ओर से ही आया 403 या 503 पेजnull, जब तक वह Cloudflare challenge न होप्रोग्राम पहले status प्रिंट करता है: ब्लॉक पेज में खोजने के लिए कोई कैप्चा नहीं होता
Arkose, GeeTest v4unsupportedCapSkip इन्हें हल नहीं कर सकता
rqdata सेट करने वाला hCaptcha Enterpriseब्राउज़र चाहिएहर task के लिए live पेज से ताज़ा rqdata पढ़ें और उसे data के रूप में SolveHcaptcha को दें
script के संकेत के बिना Friendly Captchaऐसा नतीजा जिसे ब्राउज़र चाहिएदेखें कि पेज कौन सी script लोड करता है
captcha शब्द के बिना इमेज कैप्चाnullउस साइट के लिए अपना नियम जोड़ें

आम errors

त्रुटिकारणफिक्स
ERROR_GOOGLEKEYkey शब्द explicit थी, या ऐसी key थी जो reCAPTCHA key नहीं हैkey विजेट से पढ़ें, render parameter से नहीं
ERROR_CAPTCHA_UNSOLVABLEकोई भी फेल जॉब ऐसी ही दिखती है और कोड कारण का नाम नहीं बताता। गलत reCAPTCHA वर्ज़न एक संभावित कारण हैपहले पहचाना गया प्रकार जाँचें, फिर यह देखें: API दस्तावेज़
ValidationExceptionऐसा option जो method नहीं लेता, जैसे v2 कॉल पर actionडिटेक्टर ने जो arguments लौटाए, उन्हें इस्तेमाल करें
NetworkExceptionCapSkip नहीं चल रहा, या host या पोर्ट गलत है; Windows पर यह करीब 2 सेकंड बाद आता हैCapSkip शुरू करें और कनेक्शन सेटिंग्स जाँचें, फिर यह देखें:
Solve से InvalidOperationExceptionनतीजा unsupported है या ब्राउज़र चाहिएSolve को कॉल करने से पहले इन दोनों मामलों को संभालें

सॉल्वर को किसी server पर चलाना

डिटेक्टर को CapSkip से सिर्फ़ दो चीज़ें चाहिए: एक host और एक पोर्ट। Local मोड में वह 127.0.0.1 है और आपका अपना PC। Server मोड में CapSkip आपके नेटवर्क पते या पब्लिक IP पर सुनता है, इसलिए किसी दूसरे PC, VPS या होस्टेड प्लेटफ़ॉर्म पर चल रहा डिटेक्टर उसी API से उसे कॉल कर सकता है। CapSkipClient constructor में host और पोर्ट बदल दें, कोड में और कुछ नहीं बदलता। स्थिर रिमोट एक्सेस के लिए static पब्लिक IP सुझाई जाती है। Server मोड फिर भी आपका अपना हार्डवेयर है और फिर भी बिना मीटर वाला है। ध्यान दें कि Server मोड पोर्ट को किसी भी पते के लिए खोल देता है, इसलिए जिन पतों को वहाँ पहुँचना नहीं चाहिए उनके लिए Windows Firewall में block rule जोड़ें। मोड बदलने का तरीका जानने के लिए कनेक्शन सेटिंग्स देखें।

FAQ

क्या मैं बिना कोड के बता सकता हूँ कि कोई साइट कौन सा कैप्चा इस्तेमाल करती है?

हाँ। DevTools खोलें, Network टैब पर जाएँ, पेज रीलोड करें और script के पते देखें। google.com/recaptcha या recaptcha.net की script reCAPTCHA है, challenges.cloudflare.com Turnstile है, और js.hcaptcha.com hCaptcha है। ऊपर का Console snippet यही खोज आपके लिए करता है।

Detect अनुमान की जगह ब्राउज़र चाहिए वाला नतीजा क्यों लौटाता है?

क्योंकि गलत अनुमान पर एक पूरा हल बेकार जाता है और ऐसा टोकन मिलता है जिसे साइट बिना किसी संकेत के ठुकरा देती है। जब static HTML में sitekey, GeeTest challenge या Friendly Captcha वर्ज़न न हो, तो यह बता देना फेल हुई जॉब से जल्दी निपटता है।

क्या CapSkip hCaptcha हल करता है?

हाँ। CapSkip अपने HTTP API के ज़रिए method=hcaptcha के साथ hCaptcha के checkbox, invisible और Enterprise विजेट हल करता है, जैसा कि API दस्तावेज़ बताते हैं। .NET SDK में अभी कोई hCaptcha कॉल नहीं है, इसलिए ऊपर वाला SolveHcaptcha जॉब को खुद in.php पर पोस्ट करता है। FunCaptcha, Arkose और GeeTest v4 अब भी सपोर्टेड नहीं हैं, और डिटेक्टर इन्हें unsupported बताता है ताकि प्रोग्राम उस पेज पर फेल होने के बजाय उसे छोड़ सके।

क्या कोई होस्टेड प्लेटफ़ॉर्म मेरे CapSkip के साथ डिटेक्टर इस्तेमाल कर सकता है?

हाँ, Server मोड के ज़रिए। CapSkip को अपने नेटवर्क या पब्लिक IP पर सुनने के लिए सेट करें, CapSkipClient constructor को उसी host और पोर्ट पर पॉइंट करें, और डिटेक्टर तथा Solve कॉल वैसे ही काम करते हैं जैसे एक PC पर। सॉल्वर आपके हार्डवेयर पर ही रहता है।

संक्षेप में

C# में कैप्चा डिटेक्टर इसलिए अपनी जगह बनाता है कि वह पहले चलता है: कैप्चा का प्रकार पहचानें, फिर हल करें। script और markup को तय क्रम में पढ़ें, arguments सही method को दें, और null, ब्राउज़र चाहिए वाले नतीजे और unsupported को जवाब मानें, crash नहीं। अपनी मशीन पर CapSkip के साथ किसी पेज को जाँचते समय कोई मीटर नहीं चल रहा होता, और इसी वजह से कैप्चा सॉल्वर उस crawl के लिए व्यावहारिक बनता है जो हर कुछ मिनट में एक नई साइट से मिलता है। .NET SDK के बारे में खुद जानना हो, तो यह पेज देखें: C# कैप्चा सॉल्वर पेज.

अन्य भाषाओं में यही डिटेक्टर: