From 69bc48bec067a3acc0375c66b8bbd4ee841ef232 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 23 Jun 2026 14:09:21 +0000 Subject: [PATCH] fix: parse VK captcha error without captcha_sid/captcha_img VK's error_code:14 ("Captcha need") response now omits captcha_sid and captcha_img, providing only redirect_uri with an embedded session_token for the Smart Captcha v2 flow. ParseVkCaptchaError returned nil on the missing fields, so the auto-solver was never invoked and every stream failed auth. Make captcha_sid and captcha_img optional; the v2 solver only needs redirect_uri + session_token. Also build Android for all ABIs (arm64 prioritized first, then arm, x86, x86_64) in CI and release workflows, client and server cores alike. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01HhExd8rxbEfhzJjBiawRQH --- .github/workflows/ci.yml | 50 +++++++++++++++++++++---------- .github/workflows/release.yml | 56 ++++++++++++++++++++++++----------- client/main.go | 25 ++++++---------- client/main_test.go | 52 ++++++++++++++++++++++++++++++++ 4 files changed, 135 insertions(+), 48 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c913016..43d00ff 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -60,14 +60,11 @@ jobs: name: Build binaries runs-on: ubuntu-latest strategy: + fail-fast: false matrix: include: - - goos: linux - goarch: amd64 - cgo: 0 - - goos: darwin - goarch: arm64 - cgo: 0 + # Android first — arm64 is the primary/priority target, then the + # remaining Android ABIs (arm, x86, x86_64). - goos: android goarch: arm64 cgo: 1 @@ -76,7 +73,20 @@ jobs: goarch: arm cgo: 1 api: 21 - # you can add more (android/arm, windows, etc.) later + - goos: android + goarch: 386 + cgo: 1 + api: 21 + - goos: android + goarch: amd64 + cgo: 1 + api: 21 + - goos: linux + goarch: amd64 + cgo: 0 + - goos: darwin + goarch: arm64 + cgo: 0 steps: - name: Check out code @@ -117,14 +127,24 @@ jobs: TOOLCHAIN_BIN="$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin" - if [ "$GOARCH" = "arm64" ]; then - export CC="$TOOLCHAIN_BIN/aarch64-linux-android${ANDROID_API}-clang" - elif [ "$GOARCH" = "arm" ]; then - export CC="$TOOLCHAIN_BIN/armv7a-linux-androideabi${ANDROID_API}-clang" - else - echo "Unsupported ANDROID GOARCH=$GOARCH" - exit 1 - fi + case "$GOARCH" in + arm64) + export CC="$TOOLCHAIN_BIN/aarch64-linux-android${ANDROID_API}-clang" + ;; + arm) + export CC="$TOOLCHAIN_BIN/armv7a-linux-androideabi${ANDROID_API}-clang" + ;; + 386) + export CC="$TOOLCHAIN_BIN/i686-linux-android${ANDROID_API}-clang" + ;; + amd64) + export CC="$TOOLCHAIN_BIN/x86_64-linux-android${ANDROID_API}-clang" + ;; + *) + echo "Unsupported ANDROID GOARCH=$GOARCH" + exit 1 + ;; + esac echo "Using Android NDK CC=$CC" fi diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3013fe9..b9427ba 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -13,9 +13,29 @@ jobs: build: name: Build binaries runs-on: ubuntu-latest - strategy: + # Fail-fast disabled so a single arch failure does not cancel the + # priority android/arm64 build or the other targets. + fail-fast: false matrix: include: + # Android first — arm64 is the primary/priority target, then the + # remaining Android ABIs (arm, x86, x86_64). + - goos: android + goarch: arm64 + cgo: 1 + api: 21 + - goos: android + goarch: arm + cgo: 1 + api: 21 + - goos: android + goarch: 386 + cgo: 1 + api: 21 + - goos: android + goarch: amd64 + cgo: 1 + api: 21 - goos: linux goarch: amd64 cgo: 0 @@ -59,14 +79,6 @@ jobs: - goos: windows goarch: 386 cgo: 0 - - goos: android - goarch: arm64 - cgo: 1 - api: 21 - - goos: android - goarch: arm - cgo: 1 - api: 21 - goos: freebsd goarch: amd64 cgo: 0 @@ -110,14 +122,24 @@ jobs: TOOLCHAIN_BIN="$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin" - if [ "$GOARCH" = "arm64" ]; then - export CC="$TOOLCHAIN_BIN/aarch64-linux-android${ANDROID_API}-clang" - elif [ "$GOARCH" = "arm" ]; then - export CC="$TOOLCHAIN_BIN/armv7a-linux-androideabi${ANDROID_API}-clang" - else - echo "Unsupported ANDROID GOARCH=$GOARCH" - exit 1 - fi + case "$GOARCH" in + arm64) + export CC="$TOOLCHAIN_BIN/aarch64-linux-android${ANDROID_API}-clang" + ;; + arm) + export CC="$TOOLCHAIN_BIN/armv7a-linux-androideabi${ANDROID_API}-clang" + ;; + 386) + export CC="$TOOLCHAIN_BIN/i686-linux-android${ANDROID_API}-clang" + ;; + amd64) + export CC="$TOOLCHAIN_BIN/x86_64-linux-android${ANDROID_API}-clang" + ;; + *) + echo "Unsupported ANDROID GOARCH=$GOARCH" + exit 1 + ;; + esac echo "Using Android NDK CC=$CC" fi diff --git a/client/main.go b/client/main.go index 1310559..6e1bdaf 100644 --- a/client/main.go +++ b/client/main.go @@ -455,24 +455,17 @@ func ParseVkCaptchaError(errData map[string]interface{}) *VkCaptchaError { 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 - } + // Extract captcha_sid (optional: the VK Smart Captcha v2 flow only relies on + // redirect_uri + session_token and no longer returns captcha_sid). + var captchaSid string + if sid, sidOk := errData["captcha_sid"].(string); sidOk { + captchaSid = sid + } else if sidNum, sidNumOk := errData["captcha_sid"].(float64); sidNumOk { + captchaSid = fmt.Sprintf("%.0f", sidNum) } - // Extract captcha_img - captchaImg, ok := errData["captcha_img"].(string) - if !ok { - log.Printf("missing captcha_img in captcha error data") - return nil - } + // Extract captcha_img (optional: absent in the v2 redirect_uri flow). + captchaImg, _ := errData["captcha_img"].(string) // Extract error_msg errorMsg, ok := errData["error_msg"].(string) diff --git a/client/main_test.go b/client/main_test.go index e0f2d77..1a49653 100644 --- a/client/main_test.go +++ b/client/main_test.go @@ -59,3 +59,55 @@ func TestCaptchaSolveModeForAttempt(t *testing.T) { } }) } + +func TestParseVkCaptchaErrorV2RedirectOnly(t *testing.T) { + t.Parallel() + + // VK's current error_code:14 response no longer includes captcha_sid or + // captcha_img; it only carries redirect_uri with an embedded session_token. + errData := map[string]interface{}{ + "error_code": float64(14), + "error_msg": "Captcha need", + "redirect_uri": "https://id.vk.ru/not_robot_captcha?domain=vk.com" + + "&session_token=abc.def.ghi&variant=popup&blank=1", + } + + captchaErr := ParseVkCaptchaError(errData) + if captchaErr == nil { + t.Fatal("expected captcha error to be parsed, got nil") + } + if !captchaErr.IsCaptchaError() { + t.Fatalf("expected IsCaptchaError to be true, got code=%d redirect=%q session=%q", + captchaErr.ErrorCode, captchaErr.RedirectURI, captchaErr.SessionToken) + } + if captchaErr.SessionToken != "abc.def.ghi" { + t.Fatalf("expected session_token to be extracted from redirect_uri, got %q", captchaErr.SessionToken) + } + if captchaErr.CaptchaSid != "" { + t.Fatalf("expected empty captcha_sid, got %q", captchaErr.CaptchaSid) + } +} + +func TestParseVkCaptchaErrorLegacySid(t *testing.T) { + t.Parallel() + + // Legacy format with captcha_sid/captcha_img must still parse. + errData := map[string]interface{}{ + "error_code": float64(14), + "error_msg": "Captcha needed", + "captcha_sid": "123456789", + "captcha_img": "https://api.vk.com/captcha.php?sid=123456789", + "redirect_uri": "https://id.vk.com/not_robot_captcha?session_token=tok", + } + + captchaErr := ParseVkCaptchaError(errData) + if captchaErr == nil { + t.Fatal("expected captcha error to be parsed, got nil") + } + if captchaErr.CaptchaSid != "123456789" { + t.Fatalf("expected captcha_sid 123456789, got %q", captchaErr.CaptchaSid) + } + if captchaErr.CaptchaImg == "" { + t.Fatal("expected captcha_img to be preserved") + } +}