mirror of
https://github.com/lukaszraczylo/mcp-captcharesolver.git
synced 2026-10-13 00:48:34 +00:00
- detect: recaptcha_v3 now suppresses recaptcha_v2 (hidden g-recaptcha div on v3 pages no longer mis-detected as LLM-solvable); funcaptcha drops over-broad bare "arkose" substring
- llm/gemini: stub methods return ErrUnsupported{Provider:"gemini"} so audio handler errors.As guard catches it; drop unused fmt import
- mcp/tools: remove gemini from audio capability (stub); pick audio filename (.wav/.mp3) from sniffed media type; schema notes for browser/actions no longer advertise unimplemented options
- imageutil: fetchURL uses a package client with 30s Timeout instead of http.DefaultClient
- captcha/json: extractJSON brace-counts to return the first balanced object (handles prose, markdown fences, example-before-real)
- config: remove dead CAPTCHA_LLM_TEMPERATURE / Config.Temperature; solving is fixed at temperature 0
- stealth: Action.Type doc comment drops nonexistent scroll case
56 lines
2.0 KiB
Go
56 lines
2.0 KiB
Go
package captcha
|
|
|
|
import (
|
|
"regexp"
|
|
"strings"
|
|
)
|
|
|
|
var (
|
|
reRecaptchaV3 = regexp.MustCompile(`recaptcha/api\.js\?render=([\w-]+)`)
|
|
reSitekey = regexp.MustCompile(`data-sitekey="([^"]+)"`)
|
|
rePkey = regexp.MustCompile(`data-pkey="([^"]+)"`)
|
|
)
|
|
|
|
// DetectHTML scans raw page HTML and returns deterministic detections for known
|
|
// captcha providers. v3/Turnstile are reported with LLMSolvable=false.
|
|
func DetectHTML(html string) []Detection {
|
|
var out []Detection
|
|
// reCAPTCHA v3 first (script render param). v3 has no visual challenge.
|
|
if m := reRecaptchaV3.FindStringSubmatch(html); m != nil {
|
|
out = append(out, Detection{
|
|
Type: "recaptcha_v3", Sitekey: m[1], LLMSolvable: false,
|
|
Notes: "score/behavioral — not LLM-solvable; needs browser reputation",
|
|
})
|
|
} else if strings.Contains(html, "g-recaptcha") || strings.Contains(html, "google.com/recaptcha/api2") {
|
|
out = append(out, Detection{Type: "recaptcha_v2", Sitekey: firstSitekey(html), LLMSolvable: true,
|
|
Notes: "image-grid; LLM returns tiles, caller clicks + harvests token"})
|
|
}
|
|
if strings.Contains(html, "h-captcha") || strings.Contains(html, "hcaptcha.com") {
|
|
out = append(out, Detection{Type: "hcaptcha", Sitekey: firstSitekey(html), LLMSolvable: true,
|
|
Notes: "image-grid; LLM returns tiles, caller clicks + harvests token"})
|
|
}
|
|
if strings.Contains(html, "cf-turnstile") || strings.Contains(html, "challenges.cloudflare.com") {
|
|
out = append(out, Detection{Type: "turnstile", Sitekey: firstSitekey(html), LLMSolvable: false,
|
|
Notes: "proof-of-work/behavioral — not LLM-solvable"})
|
|
}
|
|
if strings.Contains(html, "arkoselabs") || strings.Contains(html, "funcaptcha") {
|
|
out = append(out, Detection{Type: "funcaptcha", Sitekey: firstPkey(html), LLMSolvable: true,
|
|
Notes: "rotation/visual — best-effort"})
|
|
}
|
|
return out
|
|
}
|
|
|
|
func firstSitekey(html string) string {
|
|
if m := reSitekey.FindStringSubmatch(html); m != nil {
|
|
return m[1]
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func firstPkey(html string) string {
|
|
if m := rePkey.FindStringSubmatch(html); m != nil {
|
|
return m[1]
|
|
}
|
|
return ""
|
|
}
|