diff --git a/client/main.go b/client/main.go index 71a86251..73dfd172 100644 --- a/client/main.go +++ b/client/main.go @@ -228,7 +228,7 @@ func applyBrowserProfileFhttp(req *fhttp.Request, profile Profile) { } func generateBrowserFp(profile Profile) string { - data := profile.UserAgent + profile.SecChUa + "1920x1080x24" + data := profile.UserAgent + profile.SecChUa + "1920x1080x24" + strconv.FormatInt(time.Now().UnixNano(), 10) h := md5.Sum([]byte(data)) return hex.EncodeToString(h[:]) } diff --git a/client/manual_captcha.go b/client/manual_captcha.go index 27f958d3..826478cd 100644 --- a/client/manual_captcha.go +++ b/client/manual_captcha.go @@ -125,6 +125,7 @@ func rewriteProxyRequest(req *http.Request, targetURL *neturl.URL) { req.Host = targetURL.Host req.Header.Del("Accept-Encoding") + req.Header.Del("TE") // Disable transfer encoding compression for _, headerName := range []string{"Origin", "Referer"} { if rewritten := rewriteProxyHeaderURL(req.Header.Get(headerName), targetURL); rewritten != "" { req.Header.Set(headerName, rewritten) @@ -341,7 +342,7 @@ func newCaptchaProxyTransport(dialer *dnsdialer.Dialer) *http.Transport { IdleConnTimeout: 90 * time.Second, TLSHandshakeTimeout: 10 * time.Second, ExpectContinueTimeout: 1 * time.Second, - ForceAttemptHTTP2: true, + ForceAttemptHTTP2: false, } if dialer != nil { transport.DialContext = dialer.DialContext @@ -458,16 +459,17 @@ func solveCaptchaViaProxy(redirectURI string, dialer *dnsdialer.Dialer) (string, rewriteProxyRequest(req.Out, targetURL) }, ErrorHandler: func(w http.ResponseWriter, r *http.Request, err error) { - log.Printf("captcha proxy error for %s: %v", r.URL.String(), err) + log.Printf("[Captcha Proxy] ERROR for %s %s: %v", r.Method, r.URL.String(), err) w.Header().Set("Content-Type", "text/html; charset=utf-8") w.WriteHeader(http.StatusBadGateway) - _, _ = fmt.Fprintf(w, `
%v
`, err) + _, _ = fmt.Fprintf(w, `%s %s
%v
`, r.Method, r.URL.String(), err) }, ModifyResponse: func(res *http.Response) error { rewriteProxyCookies(res.Header) if res.StatusCode >= 300 && res.StatusCode < 400 { if loc := res.Header.Get("Location"); loc != "" { + log.Printf("[Captcha Proxy] Redirecting to: %s", loc) if rewritten, ok := rewriteProxyRedirectLocation(loc, targetURL); ok { res.Header.Set("Location", rewritten) } else { @@ -477,7 +479,13 @@ func solveCaptchaViaProxy(redirectURI string, dialer *dnsdialer.Dialer) (string, } contentType := res.Header.Get("Content-Type") - shouldInspectBody := strings.Contains(contentType, "text/html") || strings.Contains(res.Request.URL.Path, "captchaNotRobot.check") + contentEncoding := res.Header.Get("Content-Encoding") + log.Printf("[Captcha Proxy] %s %d | Content-Type: %q, Encoding: %q", res.Request.Method, res.StatusCode, contentType, contentEncoding) + + shouldInspectBody := strings.Contains(contentType, "text/html") || + strings.Contains(contentType, "application/xhtml+xml") || + strings.Contains(res.Request.URL.Path, "captchaNotRobot.check") + if !shouldInspectBody { return nil } @@ -517,6 +525,8 @@ func solveCaptchaViaProxy(redirectURI string, dialer *dnsdialer.Dialer) (string, "Cross-Origin-Embedder-Policy", "Cross-Origin-Resource-Policy", "X-Frame-Options", + "Strict-Transport-Security", + "Alt-Svc", } { res.Header.Del(headerName) } @@ -559,7 +569,9 @@ func solveCaptchaViaProxy(redirectURI string, dialer *dnsdialer.Dialer) (string, }) mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + log.Printf("[Captcha Proxy] HTTP %s %s", r.Method, r.URL.String()) if r.URL.Path == "/" && targetURL.Path != "" && targetURL.Path != "/" && r.URL.RawQuery == "" { + log.Printf("[Captcha Proxy] Redirecting ROOT to: %s", localCaptchaURLForTarget(targetURL)) http.Redirect(w, r, localCaptchaURLForTarget(targetURL), http.StatusTemporaryRedirect) return } diff --git a/client/slider_captcha.go b/client/slider_captcha.go index 166a6abd..b4644c46 100644 --- a/client/slider_captcha.go +++ b/client/slider_captcha.go @@ -153,7 +153,7 @@ func (s *captchaNotRobotSession) requestComponentDone() error { } func (s *captchaNotRobotSession) requestCheckboxCheck() (*captchaCheckResult, error) { - return s.requestCheck("[]", base64.StdEncoding.EncodeToString([]byte("{}"))) + return s.requestCheck(generateSliderCursor(0, 1), base64.StdEncoding.EncodeToString([]byte("{}"))) } func (s *captchaNotRobotSession) requestSliderContent(sliderSettings string) (*sliderCaptchaContent, error) { @@ -267,6 +267,22 @@ func callCaptchaNotRobotWithSliderPOC( sliderContent, err := session.requestSliderContent(sliderSettings) if err != nil { + log.Printf( + "[STREAM %d] [Captcha] Slider getContent failed (status: %v). Trying to solve as a checkbox instead...", + streamID, + err, + ) + // Fallback: maybe it's just a checkbox that needs a human-like check + time.Sleep(300 * time.Millisecond) + finalCheck, err2 := session.requestCheckboxCheck() + if err2 == nil && finalCheck.Status == "OK" { + if finalCheck.SuccessToken == "" { + return "", fmt.Errorf("success_token not found in fallback check") + } + log.Printf("[STREAM %d] [Captcha] Fallback checkbox check succeeded!", streamID) + session.requestEndSession() + return finalCheck.SuccessToken, nil + } return "", fmt.Errorf("check status: %s (slider getContent failed: %w)", initialCheck.Status, err) }