From 014f964ad9dee3de9b9d1d7e09e8b4cc2f669e87 Mon Sep 17 00:00:00 2001 From: smediainfo Date: Thu, 25 Jun 2026 10:18:43 +0300 Subject: [PATCH] fix(vkauth): fast-fail on terminal call/link errors (9000/9008, anonymous-only, full) --- .../provider/vk/internal/vkauth/client.go | 4 +- .../provider/vk/internal/vkauth/token_call.go | 38 +++++++++++++++++++ internal/provider/vk/internal/vkauth/types.go | 15 ++++++++ 3 files changed, 56 insertions(+), 1 deletion(-) diff --git a/internal/provider/vk/internal/vkauth/client.go b/internal/provider/vk/internal/vkauth/client.go index 23cdb20..0e4b754 100644 --- a/internal/provider/vk/internal/vkauth/client.go +++ b/internal/provider/vk/internal/vkauth/client.go @@ -257,7 +257,9 @@ func (c *Client) fetch(ctx context.Context, link string, streamID int) (string, lastErr = err c.log.Warnf("[STREAM %d] [VK Auth] Failed with client_id=%s: %v", streamID, creds.ClientID, err) - if errors.Is(err, ErrCaptchaWaitRequired) || errors.Is(err, ErrFatalCaptchaNoStreams) { + if errors.Is(err, ErrCaptchaWaitRequired) || errors.Is(err, ErrFatalCaptchaNoStreams) || + errors.Is(err, ErrInvalidJoinLink) || errors.Is(err, ErrAnonymousBlocked) || + errors.Is(err, ErrCallFull) { return "", "", nil, err } es := err.Error() diff --git a/internal/provider/vk/internal/vkauth/token_call.go b/internal/provider/vk/internal/vkauth/token_call.go index 17ab31c..600e8d4 100644 --- a/internal/provider/vk/internal/vkauth/token_call.go +++ b/internal/provider/vk/internal/vkauth/token_call.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" neturl "net/url" + "strings" "time" "github.com/samosvalishe/free-turn-proxy/internal/provider/vk/internal/browserprofile" @@ -43,6 +44,10 @@ func (c *Client) fetchCallToken( data = retryData continue } + if termErr := classifyLinkError(errObj); termErr != nil { + c.log.Errorf("[STREAM %d] [VK Auth] terminal link error: %v", streamID, termErr) + return "", termErr + } return "", fmt.Errorf("VK API error: %v", errObj) } @@ -58,6 +63,39 @@ func (c *Client) fetchCallToken( } } +// classifyLinkError распознаёт ТЕРМИНАЛЬНЫЕ ответы VK в join-флоу и возвращает +// соответствующий sentinel (или nil, если ошибку можно ретраить дальше по +// client_id). Терминал = ни client_id, ни captcha не помогут, поэтому fast-fail +// вместо бесконечного цикла "решить captcha -> ошибка -> следующий client_id". +// Коды VK иногда плавают, поэтому где можно матчим и по тексту error_msg. +// +// Транзиентные (5 auth-failed, 29/9/6 rate/flood, 14 captcha) сюда НЕ попадают — +// их гасить нельзя, иначе можно убить рабочее подключение. +func classifyLinkError(errObj map[string]any) error { + code := 0 + if f, ok := errObj["error_code"].(float64); ok { + code = int(f) + } + msg := "" + if s, ok := errObj["error_msg"].(string); ok { + msg = strings.ToLower(s) + } + switch { + // Битая/мёртвая ссылка: 9008 Join link is not valid, 9000 Call not found. + case code == 9000 || code == 9008 || + strings.Contains(msg, "not valid") || strings.Contains(msg, "not found"): + return ErrInvalidJoinLink + // Звонок живой, но анонимный вход запрещён ("только авторизованные"). + // Матчим по "anonym" (не по "authoriz" — коллизия с code 5 auth-failed). + case strings.Contains(msg, "anonym"): + return ErrAnonymousBlocked + // Звонок переполнен. + case strings.Contains(msg, "full"): + return ErrCallFull + } + return nil +} + // solveCaptcha выполняет одну попытку решения captcha и возвращает тело POST // для следующего retry или ошибку при исчерпании всех режимов. func (c *Client) solveCaptcha( diff --git a/internal/provider/vk/internal/vkauth/types.go b/internal/provider/vk/internal/vkauth/types.go index 5059bc8..f64badb 100644 --- a/internal/provider/vk/internal/vkauth/types.go +++ b/internal/provider/vk/internal/vkauth/types.go @@ -59,4 +59,19 @@ var ( ErrCaptchaWaitRequired = errors.Join(provider.ErrBackoffActive, errors.New("CAPTCHA_WAIT_REQUIRED")) ErrFatalCaptchaNoStreams = errors.Join(provider.ErrFatalNoStreams, errors.New("FATAL_CAPTCHA_FAILED_NO_STREAMS")) ErrLockoutActive = errors.New("global lockout active") + + // Terminal call/link errors: retrying other client_ids or solving the captcha + // won't help — only the user changing the link / call settings will. We fast- + // fail on these instead of looping "solve captcha -> error -> next credential" + // forever, and the joined provider.ErrFatalNoStreams lets the host surface a + // specific, actionable message. + // + // ErrInvalidJoinLink: VK 9008 "Join link is not valid" (broken link) or 9000 + // "Call not found" (the call ended / no longer exists). + ErrInvalidJoinLink = errors.Join(provider.ErrFatalNoStreams, errors.New("INVALID_JOIN_LINK")) + // ErrAnonymousBlocked: the call is live but anonymous join is disabled + // ("authorized users only") — the user must allow joining by link in VK. + ErrAnonymousBlocked = errors.Join(provider.ErrFatalNoStreams, errors.New("ANON_BLOCKED")) + // ErrCallFull: the call has reached its participant limit. + ErrCallFull = errors.Join(provider.ErrFatalNoStreams, errors.New("CALL_FULL")) )