mirror of
https://github.com/hackdiaz-dev/free-turn-proxy.git
synced 2026-10-12 09:41:53 +02:00
feat(ios): ручной fallback решения VK captcha через презентер приложения
Когда авто-решатель captcha не справился, прокидываем в vk-провайдер ManualSolverFunc, который переиспользует SolveViaProxy и сигналит UI через gomobile-интерфейс CaptchaPresenter (Show/Hide). Без презентера поведение прежнее. Файлы библиотеки не затронуты - мостик в новом файле vk/iosmanual.go.
This commit is contained in:
@@ -0,0 +1,52 @@
|
||||
package vk
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"sync"
|
||||
|
||||
"github.com/samosvalishe/free-turn-proxy/internal/provider/vk/internal/captcha"
|
||||
manualcaptcha "github.com/samosvalishe/free-turn-proxy/internal/provider/vk/internal/captcha/manual"
|
||||
)
|
||||
|
||||
// iosManualURL — адрес локального прокси manual-captcha, который поднимает
|
||||
// SolveViaProxy. Должен совпадать с портом из пакета manual (captchaListenPort).
|
||||
// Приложение наводит на него WebView, пока пользователь решает captcha.
|
||||
const iosManualURL = "http://127.0.0.1:8765"
|
||||
|
||||
// iosManualMu сериализует ручное решение captcha: пользователю показываем
|
||||
// одновременно только одно окно, даже если в captcha упёрлись сразу несколько
|
||||
// стримов/кредов.
|
||||
var iosManualMu sync.Mutex
|
||||
|
||||
// IOSManualSolver строит ManualSolverFunc для мобильного приложения. Извлечение
|
||||
// success_token переиспользует manualcaptcha.SolveViaProxy — ту же логику, что и
|
||||
// десктопный CLI (локальный прокси сам ловит токен из ответов VK). show
|
||||
// вызывается с URL прокси перед показом captcha (приложение должно открыть
|
||||
// WebView на этот адрес), hide — после завершения, чтобы закрыть окно.
|
||||
//
|
||||
// Функция предназначена только для ios-биндинга и нигде в библиотеке/CLI не
|
||||
// вызывается; поведение vk-провайдера для прочих вызывающих не меняется.
|
||||
func IOSManualSolver(show func(url string), hide func()) ManualSolverFunc {
|
||||
return func(ctx context.Context, e *captcha.Error, dialer net.Dialer) (string, string, error) {
|
||||
if e.RedirectURI == "" {
|
||||
return "", "", fmt.Errorf("manual captcha: no redirect_uri")
|
||||
}
|
||||
|
||||
// Один WebView за раз: SolveViaProxy слушает фиксированный порт, да и
|
||||
// показывать пользователю несколько captcha разом смысла нет.
|
||||
iosManualMu.Lock()
|
||||
defer iosManualMu.Unlock()
|
||||
|
||||
if show != nil {
|
||||
show(iosManualURL)
|
||||
}
|
||||
if hide != nil {
|
||||
defer hide()
|
||||
}
|
||||
|
||||
token, err := manualcaptcha.SolveViaProxy(ctx, e.RedirectURI, dialer)
|
||||
return token, "", err
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package ios
|
||||
|
||||
import "sync"
|
||||
|
||||
// CaptchaPresenter — мост в UI приложения для ручного решения captcha. gomobile
|
||||
// генерирует из этого интерфейса ObjC-протокол IosCaptchaPresenter, который
|
||||
// реализует Swift. Вызывается из Go, когда авто-решатель captcha не справился и
|
||||
// нужен ручной fallback.
|
||||
//
|
||||
// - Show(url): открыть WebView на локальном прокси-адресе url, где пользователь
|
||||
// решает captcha. Метод блокирующим быть не обязан.
|
||||
// - Hide(): captcha решена или отменена — закрыть окно.
|
||||
type CaptchaPresenter interface {
|
||||
Show(url string)
|
||||
Hide()
|
||||
}
|
||||
|
||||
var (
|
||||
captchaMu sync.RWMutex
|
||||
captchaPresenter CaptchaPresenter
|
||||
)
|
||||
|
||||
// SetCaptchaPresenter регистрирует UI-презентер ручной captcha. Передайте nil,
|
||||
// чтобы отключить ручной fallback — тогда при провале авто-captcha поток просто
|
||||
// упадёт, как раньше. Вызывать один раз при старте приложения, до Start.
|
||||
func SetCaptchaPresenter(p CaptchaPresenter) {
|
||||
captchaMu.Lock()
|
||||
captchaPresenter = p
|
||||
captchaMu.Unlock()
|
||||
}
|
||||
|
||||
func currentCaptchaPresenter() CaptchaPresenter {
|
||||
captchaMu.RLock()
|
||||
defer captchaMu.RUnlock()
|
||||
return captchaPresenter
|
||||
}
|
||||
+9
-4
@@ -193,9 +193,14 @@ func Start(link, peer, dns, listen, transport, obfKey string) error {
|
||||
|
||||
var connectedStreams atomic.Int32
|
||||
|
||||
// nil solver — manual captcha через браузер на мобильном недоступна.
|
||||
// При captcha-блоке поток упадёт с ErrFatalNoStreams; пользователь
|
||||
// должен переподключиться позже.
|
||||
// Если приложение зарегистрировало презентер — включаем ручной fallback
|
||||
// captcha (авто-решатель пробуется первым, при провале показываем WebView).
|
||||
// Без презентера solver == nil: при captcha-блоке поток упадёт, как раньше.
|
||||
var solver vk.ManualSolverFunc
|
||||
if p := currentCaptchaPresenter(); p != nil {
|
||||
solver = vk.IOSManualSolver(p.Show, p.Hide)
|
||||
}
|
||||
|
||||
prov, err := vk.New(vk.Config{
|
||||
// ParseClient гарантирует непустой Links (иначе вернул бы ошибку выше).
|
||||
// Мобайл передаёт одну ссылку, провайдер берёт один join-код.
|
||||
@@ -204,7 +209,7 @@ func Start(link, peer, dns, listen, transport, obfKey string) error {
|
||||
StreamsPerCache: cfg.VK.StreamsPerCred,
|
||||
StreamsAlive: connectedStreams.Load,
|
||||
Log: logger,
|
||||
}, nil)
|
||||
}, solver)
|
||||
if err != nil {
|
||||
finalErr = fmt.Errorf("provider: %v", err)
|
||||
return
|
||||
|
||||
Reference in New Issue
Block a user