Browse Source

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 <[email protected]>
Claude-Session: https://claude.ai/code/session_01HhExd8rxbEfhzJjBiawRQH
pull/181/head
Claude 3 weeks ago
parent
commit
69bc48bec0
Failed to extract signature
  1. 50
      .github/workflows/ci.yml
  2. 56
      .github/workflows/release.yml
  3. 25
      client/main.go
  4. 52
      client/main_test.go

50
.github/workflows/ci.yml

@ -60,14 +60,11 @@ jobs:
name: Build binaries name: Build binaries
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
fail-fast: false
matrix: matrix:
include: include:
- goos: linux # Android first — arm64 is the primary/priority target, then the
goarch: amd64 # remaining Android ABIs (arm, x86, x86_64).
cgo: 0
- goos: darwin
goarch: arm64
cgo: 0
- goos: android - goos: android
goarch: arm64 goarch: arm64
cgo: 1 cgo: 1
@ -76,7 +73,20 @@ jobs:
goarch: arm goarch: arm
cgo: 1 cgo: 1
api: 21 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: steps:
- name: Check out code - name: Check out code
@ -117,14 +127,24 @@ jobs:
TOOLCHAIN_BIN="$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin" TOOLCHAIN_BIN="$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin"
if [ "$GOARCH" = "arm64" ]; then case "$GOARCH" in
export CC="$TOOLCHAIN_BIN/aarch64-linux-android${ANDROID_API}-clang" arm64)
elif [ "$GOARCH" = "arm" ]; then export CC="$TOOLCHAIN_BIN/aarch64-linux-android${ANDROID_API}-clang"
export CC="$TOOLCHAIN_BIN/armv7a-linux-androideabi${ANDROID_API}-clang" ;;
else arm)
echo "Unsupported ANDROID GOARCH=$GOARCH" export CC="$TOOLCHAIN_BIN/armv7a-linux-androideabi${ANDROID_API}-clang"
exit 1 ;;
fi 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" echo "Using Android NDK CC=$CC"
fi fi

56
.github/workflows/release.yml

@ -13,9 +13,29 @@ jobs:
build: build:
name: Build binaries name: Build binaries
runs-on: ubuntu-latest 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: matrix:
include: 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 - goos: linux
goarch: amd64 goarch: amd64
cgo: 0 cgo: 0
@ -59,14 +79,6 @@ jobs:
- goos: windows - goos: windows
goarch: 386 goarch: 386
cgo: 0 cgo: 0
- goos: android
goarch: arm64
cgo: 1
api: 21
- goos: android
goarch: arm
cgo: 1
api: 21
- goos: freebsd - goos: freebsd
goarch: amd64 goarch: amd64
cgo: 0 cgo: 0
@ -110,14 +122,24 @@ jobs:
TOOLCHAIN_BIN="$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin" TOOLCHAIN_BIN="$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin"
if [ "$GOARCH" = "arm64" ]; then case "$GOARCH" in
export CC="$TOOLCHAIN_BIN/aarch64-linux-android${ANDROID_API}-clang" arm64)
elif [ "$GOARCH" = "arm" ]; then export CC="$TOOLCHAIN_BIN/aarch64-linux-android${ANDROID_API}-clang"
export CC="$TOOLCHAIN_BIN/armv7a-linux-androideabi${ANDROID_API}-clang" ;;
else arm)
echo "Unsupported ANDROID GOARCH=$GOARCH" export CC="$TOOLCHAIN_BIN/armv7a-linux-androideabi${ANDROID_API}-clang"
exit 1 ;;
fi 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" echo "Using Android NDK CC=$CC"
fi fi

25
client/main.go

@ -455,24 +455,17 @@ func ParseVkCaptchaError(errData map[string]interface{}) *VkCaptchaError {
return nil return nil
} }
// Extract captcha_sid // Extract captcha_sid (optional: the VK Smart Captcha v2 flow only relies on
captchaSid, ok := errData["captcha_sid"].(string) // redirect_uri + session_token and no longer returns captcha_sid).
if !ok { var captchaSid string
// try numeric if sid, sidOk := errData["captcha_sid"].(string); sidOk {
if sidNum, ok2 := errData["captcha_sid"].(float64); ok2 { captchaSid = sid
captchaSid = fmt.Sprintf("%.0f", sidNum) } else if sidNum, sidNumOk := errData["captcha_sid"].(float64); sidNumOk {
} else { captchaSid = fmt.Sprintf("%.0f", sidNum)
log.Printf("missing captcha_sid in captcha error data")
return nil
}
} }
// Extract captcha_img // Extract captcha_img (optional: absent in the v2 redirect_uri flow).
captchaImg, ok := errData["captcha_img"].(string) captchaImg, _ := errData["captcha_img"].(string)
if !ok {
log.Printf("missing captcha_img in captcha error data")
return nil
}
// Extract error_msg // Extract error_msg
errorMsg, ok := errData["error_msg"].(string) errorMsg, ok := errData["error_msg"].(string)

52
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")
}
}

Loading…
Cancel
Save