Skip to content

Commit 0e357cf

Browse files
[client]: improve telemetry and captcha transport
Co-authored-by: cacggghp <zthegaec@proton.me> Co-authored-by: alexmac6574 <215134852+alexmac6574@users.noreply.github.com> Co-authored-by: antongospod <47962758+antongospod@users.noreply.github.com> Co-authored-by: gazon673games <221412414143dd@gmail.com>
1 parent 346c276 commit 0e357cf

18 files changed

Lines changed: 800 additions & 96 deletions

client/captcha.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,11 @@ func runCaptchaBrowserServer(
288288
eventLine += " ua_b64=" + base64.RawURLEncoding.EncodeToString([]byte(mode.userAgent))
289289
}
290290
fmt.Println(eventLine)
291+
if strings.HasPrefix(eventPrefix, "CAPTCHA_PENDING") {
292+
emitCaptchaPromptEvent("pending", source, captchaURL, mode.userAgent)
293+
} else {
294+
emitCaptchaPromptEvent("required", source, captchaURL, mode.userAgent)
295+
}
291296
if mode.autoOpenBrowser {
292297
openBrowser(captchaURL)
293298
}
@@ -298,6 +303,7 @@ func runCaptchaBrowserServer(
298303
case outcome = <-resultCh:
299304
case <-time.After(mode.waitTimeout):
300305
fmt.Println("CAPTCHA_EXPIRED")
306+
emitCaptchaStateEvent("expired")
301307
shutdownCtx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
302308
defer cancel()
303309
_ = server.Shutdown(shutdownCtx)
@@ -314,12 +320,14 @@ func runCaptchaBrowserServer(
314320

315321
if outcome.cancelled {
316322
fmt.Println("CAPTCHA_CANCELLED")
323+
emitCaptchaStateEvent("cancelled")
317324
return "", fmt.Errorf("captcha cancelled")
318325
}
319326
if strings.TrimSpace(outcome.value) == "" {
320327
return "", fmt.Errorf("captcha returned empty result")
321328
}
322329
fmt.Println("CAPTCHA_SOLVED")
330+
emitCaptchaStateEvent("solved")
323331
return outcome.value, nil
324332
}
325333

client/captcha_auto.go

Lines changed: 30 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package main
22

33
import (
44
"context"
5+
"crypto/md5"
56
"crypto/sha256"
67
"encoding/base64"
78
"encoding/hex"
@@ -10,12 +11,13 @@ import (
1011
"io"
1112
"log"
1213
"math/rand"
13-
"net/http"
1414
neturl "net/url"
1515
"regexp"
1616
"strconv"
1717
"strings"
1818
"time"
19+
20+
tlsclient "github.com/bogdanfinn/tls-client"
1921
)
2022

2123
type vkCaptchaError struct {
@@ -66,8 +68,13 @@ func solveVkCaptcha(ctx context.Context, captchaErr *vkCaptchaError, resolver *p
6668
return "", fmt.Errorf("no session_token in redirect_uri")
6769
}
6870
log.Printf("Solving VK Smart Captcha automatically...")
71+
client, err := resolver.newTLSHTTPClient(profile, 20*time.Second)
72+
if err != nil {
73+
return "", fmt.Errorf("failed to initialize tls client: %w", err)
74+
}
75+
defer client.CloseIdleConnections()
6976

70-
powInput, difficulty, err := fetchPowInput(ctx, captchaErr.RedirectURI, resolver, profile)
77+
powInput, difficulty, err := fetchPowInput(ctx, captchaErr.RedirectURI, client, profile)
7178
if err != nil {
7279
return "", fmt.Errorf("failed to fetch PoW input: %w", err)
7380
}
@@ -77,7 +84,7 @@ func solveVkCaptcha(ctx context.Context, captchaErr *vkCaptchaError, resolver *p
7784
return "", fmt.Errorf("failed to solve PoW")
7885
}
7986

80-
successToken, err := callCaptchaNotRobot(ctx, captchaErr.SessionToken, hash, resolver, profile)
87+
successToken, err := callCaptchaNotRobot(ctx, captchaErr.SessionToken, hash, client, profile)
8188
if err != nil {
8289
return "", fmt.Errorf("captchaNotRobot API failed: %w", err)
8390
}
@@ -86,16 +93,13 @@ func solveVkCaptcha(ctx context.Context, captchaErr *vkCaptchaError, resolver *p
8693
return successToken, nil
8794
}
8895

89-
func fetchPowInput(ctx context.Context, redirectURI string, resolver *protectedResolver, profile Profile) (string, int, error) {
90-
req, err := http.NewRequestWithContext(ctx, "GET", redirectURI, nil)
96+
func fetchPowInput(ctx context.Context, redirectURI string, client tlsclient.HttpClient, profile Profile) (string, int, error) {
97+
req, err := newFHTTPRequest(ctx, "GET", redirectURI, nil)
9198
if err != nil {
9299
return "", 0, err
93100
}
94-
applyBrowserProfile(req, profile)
101+
applyBrowserProfileFhttp(req, profile)
95102
req.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")
96-
97-
client := resolver.newHTTPClient(20 * time.Second)
98-
defer client.CloseIdleConnections()
99103
resp, err := client.Do(req)
100104
if err != nil {
101105
return "", 0, err
@@ -145,22 +149,19 @@ func callCaptchaNotRobot(
145149
ctx context.Context,
146150
sessionToken string,
147151
hash string,
148-
resolver *protectedResolver,
152+
client tlsclient.HttpClient,
149153
profile Profile,
150154
) (string, error) {
151155
vkReq := func(method string, postData string) (map[string]interface{}, error) {
152156
reqURL := "https://api.vk.ru/method/" + method + "?v=5.131"
153-
req, err := http.NewRequestWithContext(ctx, "POST", reqURL, strings.NewReader(postData))
157+
req, err := newFHTTPRequest(ctx, "POST", reqURL, []byte(postData))
154158
if err != nil {
155159
return nil, err
156160
}
157-
applyBrowserProfile(req, profile)
161+
applyBrowserProfileFhttp(req, profile)
158162
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
159163
req.Header.Set("Origin", "https://vk.ru")
160164
req.Header.Set("Referer", "https://vk.ru/")
161-
162-
client := resolver.newHTTPClient(20 * time.Second)
163-
defer client.CloseIdleConnections()
164165
httpResp, err := client.Do(req)
165166
if err != nil {
166167
return nil, err
@@ -192,8 +193,11 @@ func callCaptchaNotRobot(
192193
}
193194
time.Sleep(200 * time.Millisecond)
194195

195-
browserFp := fmt.Sprintf("%032x", rand.Int63())
196-
deviceJSON := `{"screenWidth":1920,"screenHeight":1080,"screenAvailWidth":1920,"screenAvailHeight":1032,"innerWidth":1920,"innerHeight":945,"devicePixelRatio":1,"language":"en-US","languages":["en-US"],"webdriver":false,"hardwareConcurrency":16,"deviceMemory":8,"connectionEffectiveType":"4g","notificationsPermission":"denied"}`
196+
browserFp := generateBrowserFp(profile)
197+
deviceJSON := fmt.Sprintf(
198+
`{"screenWidth":1920,"screenHeight":1080,"screenAvailWidth":1920,"screenAvailHeight":1040,"innerWidth":1920,"innerHeight":969,"devicePixelRatio":1,"language":"en-US","languages":["en-US"],"webdriver":false,"hardwareConcurrency":8,"deviceMemory":8,"connectionEffectiveType":"4g","notificationsPermission":"default","userAgent":"%s","platform":"Win32"}`,
199+
profile.UserAgent,
200+
)
197201
componentDoneData := baseParams + fmt.Sprintf(
198202
"&browser_fp=%s&device=%s",
199203
browserFp,
@@ -205,9 +209,15 @@ func callCaptchaNotRobot(
205209
}
206210
time.Sleep(200 * time.Millisecond)
207211

208-
cursorJSON := `[{"x":950,"y":500},{"x":945,"y":510},{"x":940,"y":520},{"x":938,"y":525},{"x":938,"y":525}]`
212+
cursorJSON := generateFakeCursor()
209213
answer := base64.StdEncoding.EncodeToString([]byte("{}"))
210-
debugInfo := "d44f534ce8deb56ba20be52e05c433309b49ee4d2a70602deeb17a1954257785"
214+
debugInfoBytes := md5.Sum([]byte(profile.UserAgent + strconv.FormatInt(time.Now().UnixNano(), 10)))
215+
debugInfo := hex.EncodeToString(debugInfoBytes[:])
216+
connectionDownlinkSamples := make([]string, 0, 16)
217+
for i := 0; i < 16; i++ {
218+
connectionDownlinkSamples = append(connectionDownlinkSamples, fmt.Sprintf("%.1f", 8.5+rand.Float64()*2.0))
219+
}
220+
connectionDownlink := "[" + strings.Join(connectionDownlinkSamples, ",") + "]"
211221
checkData := baseParams + fmt.Sprintf(
212222
"&accelerometer=%s&gyroscope=%s&motion=%s&cursor=%s&taps=%s&connectionRtt=%s&connectionDownlink=%s&browser_fp=%s&hash=%s&answer=%s&debug_info=%s",
213223
neturl.QueryEscape("[]"),
@@ -216,7 +226,7 @@ func callCaptchaNotRobot(
216226
neturl.QueryEscape(cursorJSON),
217227
neturl.QueryEscape("[]"),
218228
neturl.QueryEscape("[]"),
219-
neturl.QueryEscape("[9.5,9.5,9.5,9.5,9.5,9.5,9.5,9.5,9.5,9.5,9.5,9.5,9.5,9.5,9.5,9.5]"),
229+
neturl.QueryEscape(connectionDownlink),
220230
browserFp,
221231
hash,
222232
answer,

client/events.go

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
package main
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"log"
7+
"strings"
8+
"time"
9+
)
10+
11+
const proxyEventProtocolVersion = 1
12+
13+
var proxyCapabilities = []string{
14+
"auth_ready",
15+
"captcha_lockout",
16+
"manual_captcha",
17+
"tls_client",
18+
"json_events",
19+
}
20+
21+
type proxyStatusEvent struct {
22+
Type string `json:"type"`
23+
Phase string `json:"phase"`
24+
}
25+
26+
type proxyLockoutEvent struct {
27+
Type string `json:"type"`
28+
Seconds int `json:"seconds"`
29+
}
30+
31+
type proxyCaptchaEvent struct {
32+
Type string `json:"type"`
33+
State string `json:"state"`
34+
Source string `json:"source,omitempty"`
35+
URL string `json:"url,omitempty"`
36+
UserAgent string `json:"userAgent,omitempty"`
37+
}
38+
39+
type proxyCapsEvent struct {
40+
Type string `json:"type"`
41+
Version int `json:"version"`
42+
Capabilities []string `json:"capabilities"`
43+
}
44+
45+
func emitProxyCaps() {
46+
fmt.Println(
47+
"PROXY_CAPS: version=" +
48+
fmt.Sprintf("%d", proxyEventProtocolVersion) +
49+
" caps=" +
50+
strings.Join(proxyCapabilities, ","),
51+
)
52+
emitProxyEvent(proxyCapsEvent{
53+
Type: "caps",
54+
Version: proxyEventProtocolVersion,
55+
Capabilities: proxyCapabilities,
56+
})
57+
}
58+
59+
func emitProxyEvent(payload any) {
60+
encoded, err := json.Marshal(payload)
61+
if err != nil {
62+
log.Printf("failed to marshal proxy event: %s", err)
63+
return
64+
}
65+
fmt.Println("PROXY_EVENT: " + string(encoded))
66+
}
67+
68+
func emitProxyStatus(marker string) {
69+
if marker == "" {
70+
return
71+
}
72+
switch marker {
73+
case "auth_ready":
74+
proxyAuthReadyState.Store(true)
75+
case "turn_ready":
76+
proxyTurnReadyState.Store(true)
77+
case "dtls_ready", "ok":
78+
proxyTurnReadyState.Store(true)
79+
proxyDtlsReadyState.Store(true)
80+
}
81+
fmt.Println("PROXY_STATUS: " + marker)
82+
emitProxyEvent(proxyStatusEvent{
83+
Type: "status",
84+
Phase: marker,
85+
})
86+
}
87+
88+
func emitCaptchaLockoutStatus(duration time.Duration) {
89+
seconds := int(duration.Round(time.Second) / time.Second)
90+
if seconds < 1 {
91+
seconds = 1
92+
}
93+
emitProxyStatus(fmt.Sprintf("captcha_lockout %d", seconds))
94+
emitProxyEvent(proxyLockoutEvent{
95+
Type: "lockout",
96+
Seconds: seconds,
97+
})
98+
}
99+
100+
func emitCaptchaPromptEvent(state string, source string, url string, userAgent string) {
101+
if state == "" {
102+
return
103+
}
104+
emitProxyEvent(proxyCaptchaEvent{
105+
Type: "captcha",
106+
State: state,
107+
Source: strings.TrimSpace(source),
108+
URL: strings.TrimSpace(url),
109+
UserAgent: strings.TrimSpace(userAgent),
110+
})
111+
}
112+
113+
func emitCaptchaStateEvent(state string) {
114+
if state == "" {
115+
return
116+
}
117+
emitProxyEvent(proxyCaptchaEvent{
118+
Type: "captcha",
119+
State: state,
120+
})
121+
}

0 commit comments

Comments
 (0)