fix(vkauth): fast-fail on terminal call/link errors (9000/9008, anonymous-only, full)

This commit is contained in:
smediainfo
2026-06-25 10:18:43 +03:00
committed by sMedia.tech
parent d5808f280a
commit 014f964ad9
3 changed files with 56 additions and 1 deletions
@@ -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()
@@ -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(
@@ -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"))
)