mirror of
https://github.com/hackdiaz-dev/free-turn-proxy.git
synced 2026-10-12 09:41:53 +02:00
feat(vkauth): флаг -browser и профиль Firefox
Флаг -browser chrome|firefox (default firefox, Chrome - escape-hatch), client-local для VK-auth, в freeturn:// URI не идёт. browserprofile.ForKind даёт канонные UA/JA3/Accept-Language; ApplyFhttp шлёт sec-ch-ua* только для Chrome (Firefox их не отправляет). dial.go выбирает profiles.Firefox_147 / Chrome_146 под UA. Control-plane TLS-dial переведён на MultiSplitWriteConn (фрагментация SNI).
This commit is contained in:
+2
-1
@@ -195,6 +195,7 @@ func buildProvider(cfg *config.Client, dialer net.Dialer, connected *atomic.Int3
|
||||
Link: cfg.VK.Link,
|
||||
Dialer: dialer,
|
||||
ManualOnly: cfg.VK.ManualCaptcha,
|
||||
Browser: string(cfg.VK.Browser),
|
||||
StreamsPerCache: cfg.VK.StreamsPerCred,
|
||||
StreamsAlive: connected.Load,
|
||||
Log: logger,
|
||||
@@ -239,7 +240,7 @@ func resolveClientID(cliID string) string {
|
||||
b, _ := json.MarshalIndent(lc, "", " ")
|
||||
|
||||
// Запись: первый доступный для записи путь. На Android каталог рядом с
|
||||
// бинарём (/data/app/.../lib/arm64) read-only — падаем на UserConfigDir,
|
||||
// бинарём (/data/app/.../lib/arm64) read-only - падаем на UserConfigDir,
|
||||
// затем TempDir. Иначе ID не сохраняется и ротируется на каждый запуск,
|
||||
// ломая allowlist (-clients-file) и статистику.
|
||||
for _, path := range paths {
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
| `-gen-obf-key` | `false` | напечатать новый ключ и выйти |
|
||||
| `-manual-captcha` | `false` | сразу ручной режим captcha (только `-provider vk`) |
|
||||
| `-streams-per-cred` | `10` | потоков на один кеш VK-учёток (только `-provider vk`) |
|
||||
| `-browser` | `firefox` | браузерный профиль VK-auth (UA + TLS JA3 + client hints): `chrome` \| `firefox` (только `-provider vk`) |
|
||||
| `-dns-mode` | `auto` | `plain` (UDP/53) \| `doh` \| `auto` |
|
||||
| `-dns-servers` | пусто | свои UDP/53 резолверы, `ip[:port][,ip[:port]...]` |
|
||||
| `-client-id` | авто | уникальный ID клиента (автогенерация если не задан) |
|
||||
|
||||
@@ -75,11 +75,22 @@ type ProxyOpts struct {
|
||||
Peer string // -peer: адрес серверного прокси, куда дозванивается клиент (только клиент)
|
||||
}
|
||||
|
||||
// Browser выбирает браузерный профиль (UA + TLS JA3 + client hints) для
|
||||
// control-plane запросов VK-провайдера. firefox несёт меньше client hints
|
||||
// (sec-ch-ua* - Chromium-only), chrome даёт herd-cover.
|
||||
type Browser string
|
||||
|
||||
const (
|
||||
BrowserChrome Browser = "chrome"
|
||||
BrowserFirefox Browser = "firefox"
|
||||
)
|
||||
|
||||
// VKOpts - опции VK-учёток и captcha (только клиент, провайдер "vk").
|
||||
type VKOpts struct {
|
||||
Link string // -link (нормализован до join-кода)
|
||||
StreamsPerCred int // -streams-per-cred
|
||||
ManualCaptcha bool // -manual-captcha
|
||||
Link string // -link (нормализован до join-кода)
|
||||
StreamsPerCred int // -streams-per-cred
|
||||
ManualCaptcha bool // -manual-captcha
|
||||
Browser Browser // -browser: chrome | firefox
|
||||
}
|
||||
|
||||
// ProviderOpts выбирает реализацию provider.Provider.
|
||||
@@ -176,6 +187,7 @@ func ParseClient(args []string, errOut io.Writer) (*Client, error) {
|
||||
streamsPerCred := fs.Int("streams-per-cred", defaultStreamsPerCache, "TURN-потоков на один кеш VK-creds; только -provider vk")
|
||||
debug := fs.Bool("debug", false, "подробные debug-логи")
|
||||
manualCaptcha := fs.Bool("manual-captcha", false, "ручная VK captcha в браузере вместо авто; только -provider vk")
|
||||
browser := fs.String("browser", string(BrowserFirefox), "браузерный профиль VK-auth: chrome | firefox; только -provider vk")
|
||||
dnsMode := fs.String("dns-mode", dnsModeAuto, "резолвер клиента: plain | doh | auto")
|
||||
dnsServers := fs.String("dns-servers", "", "свои UDP/53 DNS через запятую: ip[:port][,ip[:port]...]")
|
||||
clientID := fs.String("client-id", "", "уникальный ID клиента (автогенерация если не задан)")
|
||||
@@ -207,6 +219,7 @@ func ParseClient(args []string, errOut io.Writer) (*Client, error) {
|
||||
VK: VKOpts{
|
||||
StreamsPerCred: *streamsPerCred,
|
||||
ManualCaptcha: *manualCaptcha,
|
||||
Browser: Browser(*browser),
|
||||
},
|
||||
DNS: DNSOpts{
|
||||
Mode: *dnsMode,
|
||||
@@ -316,6 +329,11 @@ func ParseClient(args []string, errOut io.Writer) (*Client, error) {
|
||||
if c.VK.StreamsPerCred <= 0 {
|
||||
return nil, fmt.Errorf("-streams-per-cred must be positive")
|
||||
}
|
||||
switch c.VK.Browser {
|
||||
case BrowserChrome, BrowserFirefox:
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid -browser value %q: must be %s | %s", c.VK.Browser, BrowserChrome, BrowserFirefox)
|
||||
}
|
||||
parts := strings.Split(*link, "join/")
|
||||
link := parts[len(parts)-1]
|
||||
if idx := strings.IndexAny(link, "/?#"); idx != -1 {
|
||||
|
||||
@@ -7,11 +7,47 @@ import (
|
||||
fhttp "github.com/bogdanfinn/fhttp"
|
||||
)
|
||||
|
||||
// Kind - семейство браузера. Определяет UA, JA3-профиль и набор client hints.
|
||||
type Kind string
|
||||
|
||||
const (
|
||||
Chrome Kind = "chrome"
|
||||
Firefox Kind = "firefox"
|
||||
)
|
||||
|
||||
// KindFromString мапит строку флага -browser в Kind. Пустое/неизвестное -
|
||||
// Firefox (текущий дефолт продукта).
|
||||
func KindFromString(s string) Kind {
|
||||
if s == string(Chrome) {
|
||||
return Chrome
|
||||
}
|
||||
return Firefox
|
||||
}
|
||||
|
||||
type Profile struct {
|
||||
UserAgent string
|
||||
SecChUa string
|
||||
SecChUa string // пусто для Firefox (Chromium-only client hint)
|
||||
SecChUaMobile string
|
||||
SecChUaPlatform string
|
||||
AcceptLanguage string
|
||||
}
|
||||
|
||||
// ForKind возвращает канонический профиль для браузера. JA3 (см.
|
||||
// vkauth.clientProfile) обязан совпадать с UA отсюда, иначе рассинхрон = флаг.
|
||||
func ForKind(k Kind) Profile {
|
||||
if k == Firefox {
|
||||
return Profile{
|
||||
UserAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:147.0) Gecko/20100101 Firefox/147.0",
|
||||
AcceptLanguage: "en-US,en;q=0.5",
|
||||
}
|
||||
}
|
||||
return Profile{
|
||||
UserAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36",
|
||||
SecChUa: `"Not(A:Brand";v="99", "Google Chrome";v="146", "Chromium";v="146"`,
|
||||
SecChUaMobile: "?0",
|
||||
SecChUaPlatform: `"Windows"`,
|
||||
AcceptLanguage: "en-US,en;q=0.9",
|
||||
}
|
||||
}
|
||||
|
||||
type Saved struct {
|
||||
@@ -44,9 +80,17 @@ func Save(sp Saved) error {
|
||||
|
||||
func ApplyFhttp(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")
|
||||
// sec-ch-ua* - Chromium-only client hints. Для Firefox SecChUa пуст -
|
||||
// заголовки не ставим (UA Firefox + sec-ch-ua = мгновенный флаг).
|
||||
if profile.SecChUa != "" {
|
||||
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)
|
||||
}
|
||||
acceptLang := profile.AcceptLanguage
|
||||
if acceptLang == "" {
|
||||
acceptLang = "en-US,en;q=0.9"
|
||||
}
|
||||
req.Header.Set("Accept-Language", acceptLang)
|
||||
req.Header.Set("DNT", "1")
|
||||
}
|
||||
|
||||
@@ -11,15 +11,16 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/samosvalishe/free-turn-proxy/internal/logx"
|
||||
"github.com/samosvalishe/free-turn-proxy/internal/provider/vk/internal/browserprofile"
|
||||
"github.com/samosvalishe/free-turn-proxy/internal/randx"
|
||||
|
||||
tlsclient "github.com/bogdanfinn/tls-client"
|
||||
)
|
||||
|
||||
// Config конфигурирует Client. Нулевые значения — безопасные дефолты,
|
||||
// Config конфигурирует Client. Нулевые значения - безопасные дефолты,
|
||||
// кроме Dialer (должен быть задан явно для кастомного DNS).
|
||||
type Config struct {
|
||||
// Credentials для перебора по порядку. nil/empty → DefaultCredentials.
|
||||
// Credentials для перебора по порядку. nil/empty -> DefaultCredentials.
|
||||
Credentials []VKCredentials
|
||||
|
||||
// Dialer для HTTP-транспорта запросов к VK API.
|
||||
@@ -28,30 +29,35 @@ type Config struct {
|
||||
// ManualOnly форсирует ручной путь captcha с первой попытки.
|
||||
ManualOnly bool
|
||||
|
||||
// StreamsPerCache — делитель streamID → cacheID. <=0 → дефолт.
|
||||
// StreamsPerCache - делитель streamID -> cacheID. <=0 -> дефолт.
|
||||
StreamsPerCache int
|
||||
|
||||
// StreamsAlive возвращает число подключённых потоков; используется для
|
||||
// решения, является ли исчерпанная captcha фатальной или только throttle.
|
||||
// nil → 1.
|
||||
// nil -> 1.
|
||||
StreamsAlive func() int32
|
||||
|
||||
// AutoSolver / ManualSolver — подключаемые решалки captcha. nil отключает
|
||||
// AutoSolver / ManualSolver - подключаемые решалки captcha. nil отключает
|
||||
// соответствующий путь (поток переходит к следующей попытке).
|
||||
AutoSolver AutoSolveFunc
|
||||
ManualSolver ManualSolveFunc
|
||||
|
||||
// Log — уровневый логгер. nil → no-op.
|
||||
// Browser - браузерный профиль (UA + JA3 + client hints) для control-plane.
|
||||
// Нулевое значение -> Chrome (исторический дефолт для прямых вызовов в тестах).
|
||||
Browser browserprofile.Kind
|
||||
|
||||
// Log - уровневый логгер. nil -> no-op.
|
||||
Log logx.Logger
|
||||
}
|
||||
|
||||
// Client — фасад VK-аутентификации и кэша реквизитов. Владеет кэшем группы
|
||||
// Client - фасад VK-аутентификации и кэша реквизитов. Владеет кэшем группы
|
||||
// потоков, глобальным throttle запросов, таймером блокировки captcha и счётчиком
|
||||
// ошибок аутентификации для инвалидации устаревших TURN-реквизитов.
|
||||
type Client struct {
|
||||
credentials []VKCredentials
|
||||
dialer net.Dialer
|
||||
manualOnly bool
|
||||
browser browserprofile.Kind
|
||||
streamsFn func() int32
|
||||
autoSolver AutoSolveFunc
|
||||
manualSolve ManualSolveFunc
|
||||
@@ -64,7 +70,7 @@ type Client struct {
|
||||
fetchMu sync.Mutex
|
||||
lastFetchTime time.Time
|
||||
|
||||
// tokenChain — 4-шаговый получатель токена для пары credentials.
|
||||
// tokenChain - 4-шаговый получатель токена для пары credentials.
|
||||
// В prod подключён (*Client).getTokenChain; тесты подменяют fake.
|
||||
tokenChain tokenChainFn
|
||||
|
||||
@@ -79,6 +85,7 @@ func New(cfg Config) *Client {
|
||||
credentials: cfg.Credentials,
|
||||
dialer: cfg.Dialer,
|
||||
manualOnly: cfg.ManualOnly,
|
||||
browser: cfg.Browser,
|
||||
streamsFn: cfg.StreamsAlive,
|
||||
autoSolver: cfg.AutoSolver,
|
||||
manualSolve: cfg.ManualSolver,
|
||||
@@ -103,7 +110,7 @@ func New(cfg Config) *Client {
|
||||
|
||||
// GetCredentials возвращает (username, password, server-addrs) для TURN-allocate,
|
||||
// обращаясь к VK (с throttle + кэшем) только при необходимости. Адреса отдаются
|
||||
// в порядке предпочтения для streamID (предпочтительный первым) — pipeline
|
||||
// в порядке предпочтения для streamID (предпочтительный первым) - pipeline
|
||||
// пробует их по очереди при allocate.
|
||||
func (c *Client) GetCredentials(ctx context.Context, link string, streamID int) (string, string, []string, error) {
|
||||
cache := c.store.Get(streamID)
|
||||
@@ -143,7 +150,7 @@ func (c *Client) GetCredentials(ctx context.Context, link string, streamID int)
|
||||
}
|
||||
|
||||
// orderAddrs возвращает копию addrs, ротированную так, что предпочтительный
|
||||
// для streamID адрес стоит первым, остальные — следом (сохраняя порядок).
|
||||
// для streamID адрес стоит первым, остальные - следом (сохраняя порядок).
|
||||
// Раскидывает primary по стримам (балансировка relay-IP), оставляя остальные
|
||||
// как фоллбэк при DPI-дропе.
|
||||
func orderAddrs(addrs []string, streamID int) []string {
|
||||
@@ -194,7 +201,7 @@ func (c *Client) LockoutUntilUnix() int64 {
|
||||
return c.lockout.Load()
|
||||
}
|
||||
|
||||
// BackoffUntilUnix реализует provider.Provider — алиас LockoutUntilUnix.
|
||||
// BackoffUntilUnix реализует provider.Provider - алиас LockoutUntilUnix.
|
||||
// vkauth-lockout глобальный (без per-stream), streamID-параметра в самом
|
||||
// методе нет; интерфейс provider.Provider определяет no-arg сигнатуру.
|
||||
func (c *Client) BackoffUntilUnix() int64 { return c.LockoutUntilUnix() }
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/samosvalishe/free-turn-proxy/internal/netconn"
|
||||
"github.com/samosvalishe/free-turn-proxy/internal/provider/vk/internal/browserprofile"
|
||||
|
||||
fhttp "github.com/bogdanfinn/fhttp"
|
||||
tlsclient "github.com/bogdanfinn/tls-client"
|
||||
@@ -14,17 +15,18 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
// clientHelloSplitAt — смещение разбиения первого TCP-сегмента TLS
|
||||
// ClientHello. SNI уезжает во второй сегмент, ТСПУ не собирает его из
|
||||
// раздробленного потока и не инжектит delayed-RST. Совпадает с TURN
|
||||
// (turndial.SplitFirstWriteConn).
|
||||
clientHelloSplitAt = 6
|
||||
clientHelloSplitDelay = 20 * time.Millisecond
|
||||
// clientHelloSplitAt - фоллбэк-offset разбиения, когда SNI в ClientHello не
|
||||
// распарсился. Совпадает с TURN STUN-split.
|
||||
clientHelloSplitAt = 6
|
||||
// clientHelloSplitDelay / clientHelloSplitJitter - пауза между сегментами
|
||||
// ClientHello (base + случайная добавка [0,jitter)) для антифингерпринта тайминга.
|
||||
clientHelloSplitDelay = 20 * time.Millisecond
|
||||
clientHelloSplitJitter = 15 * time.Millisecond
|
||||
)
|
||||
|
||||
// splitDialer оборачивает base.DialContext и дробит первый Write
|
||||
// результирующего conn (TLS ClientHello) для обхода SNI-based DPI RST.
|
||||
// Реализует proxy.ContextDialer — tls-client берёт его через
|
||||
// splitDialer оборачивает base.DialContext и дробит первый Write результирующего
|
||||
// conn (TLS ClientHello) по границам внутри SNI host_name для обхода SNI-based
|
||||
// DPI RST. Реализует proxy.ContextDialer - tls-client берёт его через
|
||||
// WithProxyDialerFactory как прямой (не прокси) дилер.
|
||||
type splitDialer struct {
|
||||
base net.Dialer
|
||||
@@ -39,17 +41,31 @@ func (d *splitDialer) DialContext(ctx context.Context, network, addr string) (ne
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &netconn.SplitFirstWriteConn{Conn: c, SplitAt: clientHelloSplitAt, Delay: clientHelloSplitDelay}, nil
|
||||
return &netconn.MultiSplitWriteConn{
|
||||
Conn: c,
|
||||
Delay: clientHelloSplitDelay,
|
||||
Jitter: clientHelloSplitJitter,
|
||||
FallbackSplitAt: clientHelloSplitAt,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// clientProfile выбирает TLS/HTTP2-профиль (JA3 + client hints) под браузер.
|
||||
// JA3 обязан совпадать с UA из browserprofile.ForKind, иначе рассинхрон = флаг.
|
||||
func (c *Client) clientProfile() profiles.ClientProfile {
|
||||
if c.browser == browserprofile.Firefox {
|
||||
return profiles.Firefox_147
|
||||
}
|
||||
return profiles.Chrome_146
|
||||
}
|
||||
|
||||
// newTLSClient строит tls-client с Chrome-fingerprint и фрагментацией
|
||||
// ClientHello на всех исходящих control-plane TLS-соединениях. Базовый дилер —
|
||||
// ClientHello на всех исходящих control-plane TLS-соединениях. Базовый дилер -
|
||||
// c.dialer (несёт DNS-резолвер dnsdial); фабрика вызывается без proxyUrl, поэтому
|
||||
// CONNECT не используется — splitDialer работает как прямой транспорт.
|
||||
// CONNECT не используется - splitDialer работает как прямой транспорт.
|
||||
func (c *Client) newTLSClient(jar tlsclient.CookieJar) (tlsclient.HttpClient, error) {
|
||||
return tlsclient.NewHttpClient(tlsclient.NewNoopLogger(),
|
||||
tlsclient.WithTimeoutSeconds(20),
|
||||
tlsclient.WithClientProfile(profiles.Chrome_146),
|
||||
tlsclient.WithClientProfile(c.clientProfile()),
|
||||
tlsclient.WithCookieJar(jar),
|
||||
tlsclient.WithProxyDialerFactory(func(_ string, timeout time.Duration, localAddr *net.TCPAddr, _ fhttp.Header, _ tlsclient.Logger) (proxy.ContextDialer, error) {
|
||||
base := c.dialer
|
||||
|
||||
@@ -15,12 +15,7 @@ import (
|
||||
// и возвращает тройку TURN-allocate. Ошибки captcha запускают настроенную цепочку
|
||||
// auto/manual solver.
|
||||
func (c *Client) getTokenChain(ctx context.Context, link string, streamID int, creds VKCredentials, jar tlsclient.CookieJar) (string, string, []string, error) {
|
||||
profile := browserprofile.Profile{
|
||||
UserAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36",
|
||||
SecChUa: `"Not(A:Brand";v="99", "Google Chrome";v="146", "Chromium";v="146"`,
|
||||
SecChUaMobile: "?0",
|
||||
SecChUaPlatform: `"Windows"`,
|
||||
}
|
||||
profile := browserprofile.ForKind(c.browser)
|
||||
|
||||
httpClient, err := c.newTLSClient(jar)
|
||||
if err != nil {
|
||||
|
||||
+17
-11
@@ -1,4 +1,4 @@
|
||||
// Package vk — провайдер TURN-реквизитов через VK Calls API.
|
||||
// Package vk - провайдер TURN-реквизитов через VK Calls API.
|
||||
//
|
||||
// Фасад над internal/provider/vk/internal/vkauth: добавляет фиксированный link (адрес
|
||||
// VK callroom) и адаптирует сигнатуру GetCredentials к provider.Provider.
|
||||
@@ -14,14 +14,15 @@ import (
|
||||
|
||||
"github.com/samosvalishe/free-turn-proxy/internal/logx"
|
||||
"github.com/samosvalishe/free-turn-proxy/internal/provider"
|
||||
"github.com/samosvalishe/free-turn-proxy/internal/provider/vk/internal/browserprofile"
|
||||
"github.com/samosvalishe/free-turn-proxy/internal/provider/vk/internal/captcha"
|
||||
manualcaptcha "github.com/samosvalishe/free-turn-proxy/internal/provider/vk/internal/captcha/manual"
|
||||
"github.com/samosvalishe/free-turn-proxy/internal/provider/vk/internal/vkauth"
|
||||
)
|
||||
|
||||
// Config — параметры VK-провайдера.
|
||||
// Config - параметры VK-провайдера.
|
||||
type Config struct {
|
||||
// Link — VK callroom join-код (нормализованный, без префикса URL).
|
||||
// Link - VK callroom join-код (нормализованный, без префикса URL).
|
||||
// Обязателен.
|
||||
Link string
|
||||
|
||||
@@ -31,24 +32,28 @@ type Config struct {
|
||||
// ManualOnly форсирует ручной путь captcha с первой попытки.
|
||||
ManualOnly bool
|
||||
|
||||
// StreamsPerCache — делитель streamID → cacheID. <=0 → дефолт (10).
|
||||
// Browser - браузерный профиль control-plane: "chrome" | "firefox".
|
||||
// Пустое -> firefox (дефолт продукта).
|
||||
Browser string
|
||||
|
||||
// StreamsPerCache - делитель streamID -> cacheID. <=0 -> дефолт (10).
|
||||
StreamsPerCache int
|
||||
|
||||
// StreamsAlive возвращает число подключённых потоков; vkauth использует
|
||||
// для решения, является ли исчерпанная captcha фатальной или throttle.
|
||||
StreamsAlive func() int32
|
||||
|
||||
// Credentials — VK app_id/secret пары; nil → vkauth.DefaultCredentials.
|
||||
// Credentials - VK app_id/secret пары; nil -> vkauth.DefaultCredentials.
|
||||
Credentials []vkauth.VKCredentials
|
||||
|
||||
// Log — уровневый логгер. nil → no-op.
|
||||
// Log - уровневый логгер. nil -> no-op.
|
||||
Log logx.Logger
|
||||
|
||||
// Debug включает debug-вывод в manual-captcha (HTTP-сервер).
|
||||
Debug bool
|
||||
}
|
||||
|
||||
// ManualSolverFunc — кастомный решатель captcha. Если nil, vkauth не пытается
|
||||
// ManualSolverFunc - кастомный решатель captcha. Если nil, vkauth не пытается
|
||||
// решать ручную captcha (поток падает на ErrFatalNoStreams при auto-fail).
|
||||
type ManualSolverFunc = vkauth.ManualSolveFunc
|
||||
|
||||
@@ -58,13 +63,13 @@ type Provider struct {
|
||||
auth *vkauth.Client
|
||||
}
|
||||
|
||||
// New создаёт VK-провайдер. solver — функция ручного решения captcha
|
||||
// (опциональная); если nil — manual captcha путь отключён.
|
||||
// New создаёт VK-провайдер. solver - функция ручного решения captcha
|
||||
// (опциональная); если nil - manual captcha путь отключён.
|
||||
func New(cfg Config, solver ManualSolverFunc) (*Provider, error) {
|
||||
if cfg.Link == "" {
|
||||
return nil, fmt.Errorf("vk: empty Link")
|
||||
}
|
||||
// captcha-пакеты — internal/ для provider/vk, поэтому подключаем
|
||||
// captcha-пакеты - internal/ для provider/vk, поэтому подключаем
|
||||
// логгер здесь, а не в cmd/client.
|
||||
captcha.SetLogger(cfg.Log)
|
||||
manualcaptcha.SetLogger(cfg.Log)
|
||||
@@ -73,6 +78,7 @@ func New(cfg Config, solver ManualSolverFunc) (*Provider, error) {
|
||||
Credentials: cfg.Credentials,
|
||||
Dialer: cfg.Dialer,
|
||||
ManualOnly: cfg.ManualOnly,
|
||||
Browser: browserprofile.KindFromString(cfg.Browser),
|
||||
StreamsPerCache: cfg.StreamsPerCache,
|
||||
StreamsAlive: cfg.StreamsAlive,
|
||||
ManualSolver: solver,
|
||||
@@ -105,7 +111,7 @@ func (p *Provider) BackoffUntilUnix() int64 { return p.auth.BackoffUntilUnix() }
|
||||
// Name реализует provider.Provider.
|
||||
func (*Provider) Name() string { return "vk" }
|
||||
|
||||
// DefaultManualSolver — стандартный manual-captcha solver, использует
|
||||
// DefaultManualSolver - стандартный manual-captcha solver, использует
|
||||
// internal/provider/vk/internal/captcha/manual (HTTP-сервер 127.0.0.1:8765 + браузер).
|
||||
func DefaultManualSolver(ctx context.Context, e *captcha.Error, d net.Dialer) (string, string, error) {
|
||||
if e.RedirectURI != "" {
|
||||
|
||||
Reference in New Issue
Block a user