From e87b50a413aa401436d73216e586d4dc47223c5a Mon Sep 17 00:00:00 2001 From: Moroka8 Date: Fri, 26 Jun 2026 14:10:37 +0700 Subject: [PATCH] fix(client): accept smart captcha redirects --- pkg/clientcore/main.go | 81 ++++++++++++++++++++++++------------- pkg/clientcore/main_test.go | 47 +++++++++++++++++++++ 2 files changed, 101 insertions(+), 27 deletions(-) diff --git a/pkg/clientcore/main.go b/pkg/clientcore/main.go index e9ef3e3..2e41e2f 100644 --- a/pkg/clientcore/main.go +++ b/pkg/clientcore/main.go @@ -435,48 +435,67 @@ type VkCaptchaError struct { CaptchaAttempt string } +func parseVKErrorCode(value interface{}) (int, bool) { + switch v := value.(type) { + case float64: + return int(v), true + case int: + return v, true + case json.Number: + n, err := v.Int64() + return int(n), err == nil + case string: + n, err := strconv.Atoi(v) + return n, err == nil + default: + return 0, false + } +} + +func parseVKString(value interface{}) (string, bool) { + switch v := value.(type) { + case string: + return v, v != "" + case float64: + return fmt.Sprintf("%.0f", v), true + case int: + return strconv.Itoa(v), true + case json.Number: + return v.String(), v.String() != "" + default: + return "", false + } +} + func ParseVkCaptchaError(errData map[string]interface{}) *VkCaptchaError { // Extract error_code - codeFloat, ok := errData["error_code"].(float64) + code, ok := parseVKErrorCode(errData["error_code"]) if !ok { log.Printf("missing error_code in captcha error data") return nil } - code := int(codeFloat) - - // Extract redirect_uri - RedirectURI, ok := errData["redirect_uri"].(string) - if !ok { - log.Printf("missing redirect_uri in captcha error data") - return nil - } // Extract captcha_sid - captchaSid, ok := errData["captcha_sid"].(string) - if !ok { - // try numeric - if sidNum, ok2 := errData["captcha_sid"].(float64); ok2 { - captchaSid = fmt.Sprintf("%.0f", sidNum) - } else { - log.Printf("missing captcha_sid in captcha error data") - return nil - } + captchaSid, _ := parseVKString(errData["captcha_sid"]) + if captchaSid == "" { + log.Printf("captcha_sid not present in captcha error data") } // Extract captcha_img - captchaImg, ok := errData["captcha_img"].(string) - if !ok { - log.Printf("missing captcha_img in captcha error data") - return nil + captchaImg, _ := parseVKString(errData["captcha_img"]) + if captchaImg == "" { + log.Printf("captcha_img not present in captcha error data") } // Extract error_msg - errorMsg, ok := errData["error_msg"].(string) - if !ok { - log.Printf("missing error_msg in captcha error data") - return nil + errorMsg, _ := parseVKString(errData["error_msg"]) + if errorMsg == "" { + log.Printf("error_msg not present in captcha error data") } + // Extract redirect_uri + RedirectURI, _ := parseVKString(errData["redirect_uri"]) + // Extract session token var sessionToken string if RedirectURI != "" { @@ -493,6 +512,14 @@ func ParseVkCaptchaError(errData map[string]interface{}) *VkCaptchaError { sessionToken = st } } + if RedirectURI == "" && captchaImg == "" { + log.Printf("missing redirect_uri and captcha_img in captcha error data") + return nil + } + if RedirectURI != "" && sessionToken == "" { + log.Printf("missing session_token in captcha redirect_uri") + return nil + } // Extract is_sound_captcha_available isSound, ok := errData["is_sound_captcha_available"].(bool) @@ -531,7 +558,7 @@ func ParseVkCaptchaError(errData map[string]interface{}) *VkCaptchaError { } func (e *VkCaptchaError) IsCaptchaError() bool { - return e.ErrorCode == 14 && e.RedirectURI != "" && e.SessionToken != "" + return e.ErrorCode == 14 && ((e.RedirectURI != "" && e.SessionToken != "") || e.CaptchaImg != "") } func solveVkCaptcha(ctx context.Context, captchaErr *VkCaptchaError, streamID int, client tlsclient.HttpClient, profile Profile, useSliderPOC bool) (string, error) { diff --git a/pkg/clientcore/main_test.go b/pkg/clientcore/main_test.go index c71efd6..034630d 100644 --- a/pkg/clientcore/main_test.go +++ b/pkg/clientcore/main_test.go @@ -59,3 +59,50 @@ func TestCaptchaSolveModeForAttempt(t *testing.T) { } }) } + +func TestParseVkCaptchaErrorSupportsSmartCaptchaRedirectOnly(t *testing.T) { + t.Parallel() + + captchaErr := ParseVkCaptchaError(map[string]interface{}{ + "error_code": float64(14), + "error_msg": "Captcha need", + "is_enabled_captcha": true, + "redirect_uri": "https://id.vk.ru/not_robot_captcha?domain=vk.com&session_token=session-123&variant=popup&blank=1", + }) + + if captchaErr == nil { + t.Fatal("expected captcha error to parse") + return + } + if !captchaErr.IsCaptchaError() { + t.Fatal("expected parsed error to be recognized as captcha") + } + if captchaErr.SessionToken != "session-123" { + t.Fatalf("expected session token from redirect_uri, got %q", captchaErr.SessionToken) + } + if captchaErr.CaptchaSid != "" { + t.Fatalf("expected missing captcha_sid to stay empty, got %q", captchaErr.CaptchaSid) + } +} + +func TestParseVkCaptchaErrorSupportsLegacyImageCaptcha(t *testing.T) { + t.Parallel() + + captchaErr := ParseVkCaptchaError(map[string]interface{}{ + "error_code": float64(14), + "error_msg": "Captcha need", + "captcha_sid": float64(12345), + "captcha_img": "https://api.vk.ru/captcha.php?sid=12345", + }) + + if captchaErr == nil { + t.Fatal("expected legacy captcha error to parse") + return + } + if !captchaErr.IsCaptchaError() { + t.Fatal("expected legacy captcha error to be recognized as captcha") + } + if captchaErr.CaptchaSid != "12345" { + t.Fatalf("expected numeric captcha_sid to parse, got %q", captchaErr.CaptchaSid) + } +}