Initial v1.1.8 Commits
This commit is contained in:
@@ -0,0 +1,655 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
mathrand "math/rand"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
neturl "net/url"
|
||||
|
||||
fhttp "github.com/bogdanfinn/fhttp"
|
||||
tlsclient "github.com/bogdanfinn/tls-client"
|
||||
)
|
||||
|
||||
const (
|
||||
captchaV2APIVersion = "5.131"
|
||||
captchaV2ScriptVersion = "1.1.1324"
|
||||
captchaV2DeviceInfo = `{"screenWidth":1920,"screenHeight":1080,"screenAvailWidth":1920,"screenAvailHeight":1080,"innerWidth":1920,"innerHeight":951,"devicePixelRatio":1,"language":"en-US","languages":["en-US","en"],"webdriver":false,"hardwareConcurrency":8,"notificationsPermission":"denied"}`
|
||||
)
|
||||
|
||||
var (
|
||||
reCaptchaV2PowInput = regexp.MustCompile(`const\s+powInput\s*=\s*"([^"]+)"`)
|
||||
reCaptchaV2Difficulty = regexp.MustCompile(`const\s+difficulty\s*=\s*(\d+)`)
|
||||
reCaptchaV2WindowInit = regexp.MustCompile(`(?s)window\.init\s*=\s*(\{.*?})\s*;`)
|
||||
reCaptchaV2ScriptSrc = regexp.MustCompile(`src="(https://[^"]+not_robot_captcha[^"]+)"`)
|
||||
reCaptchaV2DebugInfo = regexp.MustCompile(`debug_info:(?:[^"]*\|\|)?"([a-fA-F0-9]{64})"`)
|
||||
reCaptchaV2Version = regexp.MustCompile(`vkid/([0-9.]*)/not_robot_captcha\.js`)
|
||||
|
||||
errCaptchaV2RateLimit = errors.New("captcha session rate limit reached")
|
||||
errCaptchaV2Bot = errors.New("captcha bot challenge")
|
||||
|
||||
captchaV2MaxAttempts = 2
|
||||
|
||||
captchaV2DebugCache sync.Map // scriptURL -> string
|
||||
captchaV2HeaderOrder = []string{
|
||||
"host",
|
||||
"content-length",
|
||||
"sec-ch-ua-platform",
|
||||
"accept-language",
|
||||
"sec-ch-ua",
|
||||
"content-type",
|
||||
"sec-ch-ua-mobile",
|
||||
"user-agent",
|
||||
"accept",
|
||||
"origin",
|
||||
"sec-fetch-site",
|
||||
"sec-fetch-mode",
|
||||
"sec-fetch-dest",
|
||||
"referer",
|
||||
"accept-encoding",
|
||||
"priority",
|
||||
}
|
||||
captchaV2PHeaderOrder = []string{":method", ":path", ":authority", ":scheme"}
|
||||
)
|
||||
|
||||
type captchaV2Init struct {
|
||||
Data captchaV2InitData `json:"data"`
|
||||
}
|
||||
|
||||
type captchaV2InitData struct {
|
||||
ShowCaptchaType string `json:"show_captcha_type"`
|
||||
CaptchaSettings []captchaV2InitSetting `json:"captcha_settings"`
|
||||
}
|
||||
|
||||
type captchaV2InitSetting struct {
|
||||
Type string `json:"type"`
|
||||
Settings string `json:"settings"`
|
||||
}
|
||||
|
||||
type captchaV2Page struct {
|
||||
PowInput string
|
||||
PowDifficulty int
|
||||
ScriptURL string
|
||||
Init *captchaV2Init
|
||||
}
|
||||
|
||||
type captchaV2Check struct {
|
||||
Status string
|
||||
SuccessToken string
|
||||
ShowType string
|
||||
}
|
||||
|
||||
type captchaV2ShowTypeError struct {
|
||||
ShowType string
|
||||
}
|
||||
|
||||
func (e *captchaV2ShowTypeError) Error() string {
|
||||
return "captcha show type mismatch: " + e.ShowType
|
||||
}
|
||||
|
||||
type captchaV2Session struct {
|
||||
ctx context.Context
|
||||
client tlsclient.HttpClient
|
||||
profile Profile
|
||||
savedProfile *SavedProfile
|
||||
}
|
||||
|
||||
func solveVkCaptchaV2(
|
||||
ctx context.Context,
|
||||
captchaErr *VkCaptchaError,
|
||||
client tlsclient.HttpClient,
|
||||
profile Profile,
|
||||
savedProfile *SavedProfile,
|
||||
) (string, error) {
|
||||
return solveVkCaptchaV2Attempts(ctx, captchaErr, client, profile, savedProfile, captchaV2MaxAttempts)
|
||||
}
|
||||
|
||||
func solveVkCaptchaV2Attempts(
|
||||
ctx context.Context,
|
||||
captchaErr *VkCaptchaError,
|
||||
client tlsclient.HttpClient,
|
||||
profile Profile,
|
||||
savedProfile *SavedProfile,
|
||||
maxAttempts int,
|
||||
) (string, error) {
|
||||
if captchaErr == nil || captchaErr.SessionToken == "" {
|
||||
return "", fmt.Errorf("no session_token in redirect_uri")
|
||||
}
|
||||
if maxAttempts < 1 {
|
||||
maxAttempts = 1
|
||||
}
|
||||
log.Printf("[КАПЧА] Решаю VK Smart Captcha автоматически (v2, попыток=%d)...", maxAttempts)
|
||||
|
||||
s := &captchaV2Session{ctx: ctx, client: client, profile: profile, savedProfile: savedProfile}
|
||||
|
||||
for attempt := 1; attempt <= maxAttempts; attempt++ {
|
||||
token, solveErr := s.solveOnce(captchaErr)
|
||||
if solveErr == nil {
|
||||
return token, nil
|
||||
}
|
||||
log.Printf("[КАПЧА] v2 попытка %d ошибка: %v", attempt, solveErr)
|
||||
if errors.Is(solveErr, errCaptchaV2RateLimit) {
|
||||
return "", solveErr
|
||||
}
|
||||
|
||||
backoffSteps := attempt
|
||||
if backoffSteps > 10 {
|
||||
backoffSteps = 10
|
||||
}
|
||||
timer := time.NewTimer(time.Duration(backoffSteps) * 500 * time.Millisecond)
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
timer.Stop()
|
||||
return "", ctx.Err()
|
||||
case <-timer.C:
|
||||
}
|
||||
}
|
||||
return "", fmt.Errorf("v2 captcha attempts exhausted (%d)", maxAttempts)
|
||||
}
|
||||
|
||||
func (s *captchaV2Session) solveOnce(captchaErr *VkCaptchaError) (string, error) {
|
||||
html, err := s.fetchCaptchaHTML(captchaErr.RedirectURI)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
page, err := parseCaptchaV2Page(html)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if page.PowInput == "" {
|
||||
return "", errors.New("failed to find PoW settings")
|
||||
}
|
||||
|
||||
sliderSettings := ""
|
||||
if page.Init != nil {
|
||||
for _, setting := range page.Init.Data.CaptchaSettings {
|
||||
if setting.Type == "slider" {
|
||||
sliderSettings = setting.Settings
|
||||
}
|
||||
}
|
||||
}
|
||||
if page.Init != nil && page.Init.Data.ShowCaptchaType == "slider" && sliderSettings == "" {
|
||||
return "", errors.New("failed to find slider captcha settings")
|
||||
}
|
||||
|
||||
log.Printf("[КАПЧА] v2 solving pow difficulty=%d", page.PowDifficulty)
|
||||
hash := solveCaptchaPoWV2(s.ctx, page.PowInput, page.PowDifficulty)
|
||||
if hash == "" {
|
||||
return "", errors.New("captcha pow failed")
|
||||
}
|
||||
log.Printf("[КАПЧА] v2 pow solved")
|
||||
|
||||
base := captchaV2BaseValues(captchaErr.SessionToken)
|
||||
if _, settingsErr := s.captchaRequest("captchaNotRobot.settings", base); settingsErr != nil {
|
||||
return "", fmt.Errorf("captcha settings failed: %w", settingsErr)
|
||||
}
|
||||
|
||||
browserFP, err := captchaV2BrowserFP()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if s.savedProfile != nil && strings.TrimSpace(s.savedProfile.BrowserFp) != "" {
|
||||
browserFP = s.savedProfile.BrowserFp
|
||||
}
|
||||
|
||||
if m := reCaptchaV2Version.FindStringSubmatch(page.ScriptURL); len(m) > 1 {
|
||||
if m[1] != captchaV2ScriptVersion {
|
||||
log.Printf("[КАПЧА] v2 script version drift: known=%s latest=%s", captchaV2ScriptVersion, m[1])
|
||||
}
|
||||
}
|
||||
|
||||
debugInfo, err := s.fetchDebugInfo(page.ScriptURL)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to fetch debug info: %w (script_version=%s)", err, captchaV2ScriptVersion)
|
||||
}
|
||||
|
||||
showType := ""
|
||||
if page.Init != nil {
|
||||
showType = page.Init.Data.ShowCaptchaType
|
||||
}
|
||||
var token string
|
||||
for {
|
||||
log.Printf("[КАПЧА] v2 solving show_type=%s", showType)
|
||||
switch showType {
|
||||
case "slider":
|
||||
token, err = s.solveSliderCaptcha(captchaErr.SessionToken, browserFP, hash, sliderSettings, debugInfo)
|
||||
case "checkbox", "":
|
||||
token, err = s.solveCheckboxCaptcha(captchaErr.SessionToken, browserFP, hash, debugInfo)
|
||||
default:
|
||||
return "", fmt.Errorf("unsupported captcha type: %s", showType)
|
||||
}
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
if errors.Is(err, errCaptchaV2Bot) && !strings.EqualFold(showType, "slider") && sliderSettings != "" {
|
||||
log.Printf("[КАПЧА] v2 checkbox returned BOT, trying slider")
|
||||
showType = "slider"
|
||||
continue
|
||||
}
|
||||
var stErr *captchaV2ShowTypeError
|
||||
if !errors.As(err, &stErr) || stErr.ShowType == "" {
|
||||
return "", err
|
||||
}
|
||||
showType = stErr.ShowType
|
||||
}
|
||||
|
||||
if _, endErr := s.captchaRequest("captchaNotRobot.endSession", base); endErr != nil {
|
||||
log.Printf("[КАПЧА] v2 endSession failed: %v", endErr)
|
||||
}
|
||||
return token, nil
|
||||
}
|
||||
|
||||
func captchaV2BaseValues(sessionToken string) [][2]string {
|
||||
return [][2]string{
|
||||
{"session_token", sessionToken},
|
||||
{"domain", "vk.com"},
|
||||
{"adFp", ""},
|
||||
{"access_token", ""},
|
||||
}
|
||||
}
|
||||
|
||||
func captchaV2BrowserFP() (string, error) {
|
||||
b := make([]byte, 16)
|
||||
if _, err := rand.Read(b); err != nil {
|
||||
return "", fmt.Errorf("browser fp generate: %w", err)
|
||||
}
|
||||
return hex.EncodeToString(b), nil
|
||||
}
|
||||
|
||||
func (s *captchaV2Session) fetchCaptchaHTML(redirectURI string) (string, error) {
|
||||
body, err := s.doRaw(fhttp.MethodGet, redirectURI, nil, map[string]string{
|
||||
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
|
||||
"Sec-Fetch-Dest": "document",
|
||||
"Sec-Fetch-Mode": "navigate",
|
||||
"Sec-Fetch-Site": "cross-site",
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(body), nil
|
||||
}
|
||||
|
||||
func (s *captchaV2Session) fetchDebugInfo(scriptURL string) (string, error) {
|
||||
if cached, ok := captchaV2DebugCache.Load(scriptURL); ok {
|
||||
if cachedDebugInfo, ok := cached.(string); ok {
|
||||
return cachedDebugInfo, nil
|
||||
}
|
||||
captchaV2DebugCache.Delete(scriptURL)
|
||||
}
|
||||
body, err := s.doRaw(fhttp.MethodGet, scriptURL, nil, map[string]string{
|
||||
"Accept": "text/javascript,*/*",
|
||||
"Referer": "https://id.vk.com/",
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
m := reCaptchaV2DebugInfo.FindSubmatch(body)
|
||||
if len(m) < 2 {
|
||||
return "", errors.New("debug_info match not found")
|
||||
}
|
||||
v := string(m[1])
|
||||
captchaV2DebugCache.Store(scriptURL, v)
|
||||
log.Printf("[КАПЧА] v2 debug_info fetched url=%s", scriptURL)
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func parseCaptchaV2Page(html string) (*captchaV2Page, error) {
|
||||
page := &captchaV2Page{}
|
||||
|
||||
match := reCaptchaV2WindowInit.FindStringSubmatch(html)
|
||||
if len(match) < 2 {
|
||||
return nil, errors.New("captcha init json not found")
|
||||
}
|
||||
var init captchaV2Init
|
||||
if err := json.Unmarshal([]byte(match[1]), &init); err != nil {
|
||||
return nil, fmt.Errorf("captcha init json parse: %w", err)
|
||||
}
|
||||
page.Init = &init
|
||||
|
||||
match = reCaptchaV2ScriptSrc.FindStringSubmatch(html)
|
||||
if len(match) < 2 {
|
||||
return nil, errors.New("captcha script url not found")
|
||||
}
|
||||
page.ScriptURL = match[1]
|
||||
|
||||
if m := reCaptchaV2PowInput.FindStringSubmatch(html); len(m) >= 2 {
|
||||
page.PowInput = m[1]
|
||||
}
|
||||
if page.PowInput == "" {
|
||||
return page, nil
|
||||
}
|
||||
|
||||
match = reCaptchaV2Difficulty.FindStringSubmatch(html)
|
||||
if len(match) < 2 {
|
||||
return nil, errors.New("captcha difficulty const not found")
|
||||
}
|
||||
difficulty, err := strconv.Atoi(match[1])
|
||||
if err != nil || difficulty <= 0 {
|
||||
return nil, fmt.Errorf("invalid captcha difficulty %q", match[1])
|
||||
}
|
||||
page.PowDifficulty = difficulty
|
||||
return page, nil
|
||||
}
|
||||
|
||||
func (s *captchaV2Session) captchaRequest(method string, form [][2]string) (map[string]any, error) {
|
||||
endpoint := "https://api.vk.ru/method/" + method + "?v=" + captchaV2APIVersion
|
||||
body, err := s.doRaw(fhttp.MethodPost, endpoint, form, map[string]string{
|
||||
"Origin": "https://id.vk.com",
|
||||
"Referer": "https://id.vk.com/",
|
||||
"Priority": "u=1, i",
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var out map[string]any
|
||||
if err := json.Unmarshal(body, &out); err != nil {
|
||||
return nil, fmt.Errorf("captcha api decode: %w", err)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (s *captchaV2Session) performCaptchaCheck(
|
||||
sessionToken string,
|
||||
browserFP string,
|
||||
hash string,
|
||||
answerJSON string,
|
||||
cursor string,
|
||||
debugInfo string,
|
||||
) (*captchaV2Check, error) {
|
||||
values := [][2]string{
|
||||
{"session_token", sessionToken},
|
||||
{"domain", "vk.com"},
|
||||
{"adFp", ""},
|
||||
{"accelerometer", "[]"},
|
||||
{"gyroscope", "[]"},
|
||||
{"motion", "[]"},
|
||||
{"cursor", cursor},
|
||||
{"taps", "[]"},
|
||||
{"connectionRtt", "[]"},
|
||||
{"connectionDownlink", "[]"},
|
||||
{"browser_fp", browserFP},
|
||||
{"hash", hash},
|
||||
{"answer", base64.StdEncoding.EncodeToString([]byte(answerJSON))},
|
||||
{"debug_info", debugInfo},
|
||||
{"access_token", ""},
|
||||
}
|
||||
resp, err := s.captchaRequest("captchaNotRobot.check", values)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("captcha check failed: %w", err)
|
||||
}
|
||||
check, err := parseCaptchaV2Check(resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if check.ShowType != "" {
|
||||
log.Printf("[КАПЧА] v2 check status=%s show_type=%s", check.Status, check.ShowType)
|
||||
} else {
|
||||
log.Printf("[КАПЧА] v2 check status=%s", check.Status)
|
||||
}
|
||||
return check, nil
|
||||
}
|
||||
|
||||
func parseCaptchaV2Check(raw map[string]any) (*captchaV2Check, error) {
|
||||
resp, ok := raw["response"].(map[string]any)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid captcha check response: %v", raw)
|
||||
}
|
||||
out := &captchaV2Check{
|
||||
Status: captchaV2StringifyAny(resp["status"]),
|
||||
SuccessToken: captchaV2StringifyAny(resp["success_token"]),
|
||||
ShowType: captchaV2StringifyAny(resp["show_captcha_type"]),
|
||||
}
|
||||
if out.Status == "" {
|
||||
return nil, fmt.Errorf("captcha check status missing: %v", raw)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (s *captchaV2Session) solveCheckboxCaptcha(
|
||||
sessionToken string,
|
||||
browserFP string,
|
||||
hash string,
|
||||
debugInfo string,
|
||||
) (string, error) {
|
||||
deviceJSON := captchaV2DeviceInfo
|
||||
if s.savedProfile != nil && strings.TrimSpace(s.savedProfile.DeviceJSON) != "" {
|
||||
deviceJSON = s.savedProfile.DeviceJSON
|
||||
}
|
||||
if _, err := s.captchaRequest("captchaNotRobot.componentDone", [][2]string{
|
||||
{"session_token", sessionToken},
|
||||
{"domain", "vk.com"},
|
||||
{"adFp", ""},
|
||||
{"browser_fp", browserFP},
|
||||
{"device", deviceJSON},
|
||||
{"access_token", ""},
|
||||
}); err != nil {
|
||||
return "", fmt.Errorf("captcha componentDone failed: %w", err)
|
||||
}
|
||||
|
||||
select {
|
||||
case <-s.ctx.Done():
|
||||
return "", s.ctx.Err()
|
||||
case <-time.After(time.Duration(400+mathrand.Intn(250)) * time.Millisecond):
|
||||
}
|
||||
|
||||
check, err := s.performCaptchaCheck(sessionToken, browserFP, hash, "{}", "[]", debugInfo)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if check.ShowType != "" && !strings.EqualFold(check.ShowType, "checkbox") {
|
||||
return "", &captchaV2ShowTypeError{ShowType: check.ShowType}
|
||||
}
|
||||
if strings.EqualFold(check.Status, "error_limit") {
|
||||
return "", errCaptchaV2RateLimit
|
||||
}
|
||||
if strings.EqualFold(check.Status, "bot") {
|
||||
return "", fmt.Errorf("%w: checkbox captcha rejected: status=%s", errCaptchaV2Bot, check.Status)
|
||||
}
|
||||
if !strings.EqualFold(check.Status, "ok") {
|
||||
return "", fmt.Errorf("checkbox captcha rejected: status=%s", check.Status)
|
||||
}
|
||||
if check.SuccessToken == "" {
|
||||
return "", errors.New("captcha success token not found")
|
||||
}
|
||||
return check.SuccessToken, nil
|
||||
}
|
||||
|
||||
func solveCaptchaPoWV2(ctx context.Context, input string, difficulty int) string {
|
||||
if input == "" || difficulty <= 0 {
|
||||
return ""
|
||||
}
|
||||
target := strings.Repeat("0", difficulty)
|
||||
for nonce := 1; nonce <= 10_000_000; nonce++ {
|
||||
if nonce%4096 == 0 {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ""
|
||||
default:
|
||||
}
|
||||
}
|
||||
sum := sha256.Sum256([]byte(input + strconv.Itoa(nonce)))
|
||||
hashHex := hex.EncodeToString(sum[:])
|
||||
if strings.HasPrefix(hashHex, target) {
|
||||
return hashHex
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (s *captchaV2Session) doRaw(
|
||||
method string,
|
||||
endpoint string,
|
||||
form [][2]string,
|
||||
extraHeaders map[string]string,
|
||||
) ([]byte, error) {
|
||||
var body []byte
|
||||
if form != nil {
|
||||
body = []byte(captchaV2EncodeForm(form))
|
||||
}
|
||||
req, err := fhttp.NewRequestWithContext(s.ctx, method, endpoint, bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
applyBrowserProfileFhttp(req, s.profile)
|
||||
req.Header.Set("Accept", "*/*")
|
||||
req.Header.Set("Sec-Fetch-Site", "same-site")
|
||||
req.Header.Set("Sec-Fetch-Mode", "cors")
|
||||
req.Header.Set("Sec-Fetch-Dest", "empty")
|
||||
req.Header.Set("Origin", "https://vk.com")
|
||||
req.Header.Set("Referer", "https://vk.com/")
|
||||
if form != nil {
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
}
|
||||
for k, v := range extraHeaders {
|
||||
req.Header.Set(k, v)
|
||||
}
|
||||
req.Header[fhttp.HeaderOrderKey] = captchaV2HeaderOrder
|
||||
req.Header[fhttp.PHeaderOrderKey] = captchaV2PHeaderOrder
|
||||
|
||||
resp, err := s.client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
if closeErr := resp.Body.Close(); closeErr != nil {
|
||||
log.Printf("[КАПЧА] v2 close body: %s", closeErr)
|
||||
}
|
||||
}()
|
||||
return io.ReadAll(resp.Body)
|
||||
}
|
||||
|
||||
func captchaV2EncodeForm(values [][2]string) string {
|
||||
if len(values) == 0 {
|
||||
return ""
|
||||
}
|
||||
var sb strings.Builder
|
||||
for i, kv := range values {
|
||||
if i > 0 {
|
||||
sb.WriteByte('&')
|
||||
}
|
||||
sb.WriteString(captchaV2QueryEscape(kv[0]))
|
||||
sb.WriteByte('=')
|
||||
sb.WriteString(captchaV2QueryEscape(kv[1]))
|
||||
}
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func captchaV2QueryEscape(s string) string {
|
||||
const upper = "0123456789ABCDEF"
|
||||
hexDigits := func(b byte) [3]byte {
|
||||
return [3]byte{'%', upper[b>>4], upper[b&0xF]}
|
||||
}
|
||||
out := make([]byte, 0, len(s))
|
||||
for i := 0; i < len(s); i++ {
|
||||
c := s[i]
|
||||
switch {
|
||||
case c == ' ':
|
||||
out = append(out, '+')
|
||||
case ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || ('0' <= c && c <= '9') || c == '-' || c == '_' || c == '.' || c == '~':
|
||||
out = append(out, c)
|
||||
default:
|
||||
h := hexDigits(c)
|
||||
out = append(out, h[:]...)
|
||||
}
|
||||
}
|
||||
return string(out)
|
||||
}
|
||||
|
||||
func captchaV2StringifyAny(value any) string {
|
||||
switch v := value.(type) {
|
||||
case nil:
|
||||
return ""
|
||||
case string:
|
||||
return v
|
||||
case float64:
|
||||
return strconv.FormatFloat(v, 'f', -1, 64)
|
||||
case bool:
|
||||
return strconv.FormatBool(v)
|
||||
default:
|
||||
data, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
return fmt.Sprintf("%v", v)
|
||||
}
|
||||
return string(data)
|
||||
}
|
||||
}
|
||||
|
||||
// applyBrowserProfileFhttp applies browser headers to fhttp requests
|
||||
func applyBrowserProfileFhttp(req *fhttp.Request, profile Profile) {
|
||||
req.Header.Set("User-Agent", profile.UserAgent)
|
||||
req.Header.Set("sec-ch-ua", profile.SecChUa)
|
||||
req.Header.Set("sec-ch-ua-mobile", profile.SecChUaMobile)
|
||||
req.Header.Set("sec-ch-ua-platform", profile.SecChUaPlatform)
|
||||
req.Header.Set("Accept-Language", "en-US,en;q=0.9")
|
||||
req.Header.Set("DNT", "1")
|
||||
}
|
||||
|
||||
// VkCaptchaError represents a VK captcha challenge
|
||||
type VkCaptchaError struct {
|
||||
ErrorCode int
|
||||
ErrorMsg string
|
||||
CaptchaSid string
|
||||
RedirectURI string
|
||||
SessionToken string
|
||||
CaptchaTs string
|
||||
CaptchaAttempt string
|
||||
}
|
||||
|
||||
func parseVkCaptchaError(errData map[string]interface{}) *VkCaptchaError {
|
||||
codeFloat, _ := errData["error_code"].(float64)
|
||||
redirectUri, _ := errData["redirect_uri"].(string)
|
||||
errorMsg, _ := errData["error_msg"].(string)
|
||||
|
||||
captchaSid, _ := errData["captcha_sid"].(string)
|
||||
if captchaSid == "" {
|
||||
if sidNum, ok := errData["captcha_sid"].(float64); ok {
|
||||
captchaSid = fmt.Sprintf("%.0f", sidNum)
|
||||
}
|
||||
}
|
||||
|
||||
var sessionToken string
|
||||
if redirectUri != "" {
|
||||
if parsed, err := neturl.Parse(redirectUri); err == nil {
|
||||
sessionToken = parsed.Query().Get("session_token")
|
||||
}
|
||||
}
|
||||
|
||||
var captchaTs string
|
||||
if tsFloat, ok := errData["captcha_ts"].(float64); ok {
|
||||
captchaTs = fmt.Sprintf("%.0f", tsFloat)
|
||||
} else if tsStr, ok := errData["captcha_ts"].(string); ok {
|
||||
captchaTs = tsStr
|
||||
}
|
||||
|
||||
var captchaAttempt string
|
||||
if attFloat, ok := errData["captcha_attempt"].(float64); ok {
|
||||
captchaAttempt = fmt.Sprintf("%.0f", attFloat)
|
||||
} else if attStr, ok := errData["captcha_attempt"].(string); ok {
|
||||
captchaAttempt = attStr
|
||||
}
|
||||
|
||||
return &VkCaptchaError{
|
||||
ErrorCode: int(codeFloat),
|
||||
ErrorMsg: errorMsg,
|
||||
CaptchaSid: captchaSid,
|
||||
RedirectURI: redirectUri,
|
||||
SessionToken: sessionToken,
|
||||
CaptchaTs: captchaTs,
|
||||
CaptchaAttempt: captchaAttempt,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user