mirror of
https://github.com/shtorm-7/sing-box-extended.git
synced 2026-08-08 11:35:16 +03:00
Add call protocol, Rmux. Update AmneziaWG. Fixes and improvements
This commit is contained in:
244
transport/call/vk/vk_auth.go
Normal file
244
transport/call/vk/vk_auth.go
Normal file
@@ -0,0 +1,244 @@
|
||||
package vk
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/sagernet/sing-box/transport/call/common"
|
||||
"github.com/sagernet/sing/common/logger"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
)
|
||||
|
||||
type vkAuthConfig struct {
|
||||
AppID string `json:"appID"`
|
||||
ApiVersion string `json:"apiVersion"`
|
||||
AppVersion string `json:"appVersion"`
|
||||
ProtocolVersion string `json:"protocolVersion"`
|
||||
PublicKey string `json:"publicKey,omitempty"`
|
||||
OkJoinLink string `json:"okJoinLink,omitempty"`
|
||||
}
|
||||
|
||||
type vkCaptchaError struct {
|
||||
captchaSid string
|
||||
redirectURI string
|
||||
captchaTs string
|
||||
captchaAttempt string
|
||||
}
|
||||
|
||||
func RunVKAuth(dialer N.Dialer, joinLink, displayName string, logger logger.ContextLogger) (string, error) {
|
||||
client := common.HttpClient(dialer)
|
||||
httpPost := func(targetURL string, form url.Values, extraHeaders map[string]string) (map[string]interface{}, error) {
|
||||
req, _ := http.NewRequest("POST", targetURL, strings.NewReader(form.Encode()))
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
req.Header.Set("User-Agent", common.UserAgent)
|
||||
req.Header.Set("Origin", "https://vk.ru")
|
||||
req.Header.Set("Referer", "https://vk.ru/")
|
||||
for k, v := range extraHeaders {
|
||||
req.Header.Set(k, v)
|
||||
}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var result map[string]interface{}
|
||||
if err := json.Unmarshal(body, &result); err != nil {
|
||||
return nil, fmt.Errorf("json: %w (body: %s)", err, string(body[:minInt(len(body), 200)]))
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
cfg := &vkAuthConfig{
|
||||
AppID: "6287487",
|
||||
ApiVersion: "5.282",
|
||||
AppVersion: "1.1",
|
||||
ProtocolVersion: "5",
|
||||
}
|
||||
logger.Debug(fmt.Sprintf("vk-auth: appID=%s api=%s appVersion=%s proto=%s", cfg.AppID, cfg.ApiVersion, cfg.AppVersion, cfg.ProtocolVersion))
|
||||
logger.Info("vk-auth: getting anonymous token")
|
||||
anonResp, err := httpPost("https://login.vk.ru/?act=get_anonym_token", url.Values{
|
||||
"client_id": {cfg.AppID},
|
||||
}, nil)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("get_anonym_token: %w", err)
|
||||
}
|
||||
dataMap, _ := anonResp["data"].(map[string]interface{})
|
||||
accessToken, _ := dataMap["access_token"].(string)
|
||||
if accessToken == "" {
|
||||
return "", fmt.Errorf("empty access_token: %v", anonResp)
|
||||
}
|
||||
logger.Debug("vk-auth: anon token OK")
|
||||
auth := map[string]string{"Authorization": "Bearer " + accessToken}
|
||||
logger.Info("vk-auth: getting call settings")
|
||||
settingsResp, err := httpPost("https://api.vk.ru/method/calls.getSettings", url.Values{
|
||||
"v": {cfg.ApiVersion},
|
||||
}, auth)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("calls.getSettings: %w", err)
|
||||
}
|
||||
if respObj, ok := settingsResp["response"].(map[string]interface{}); ok {
|
||||
if settings, ok := respObj["settings"].(map[string]interface{}); ok {
|
||||
if pk, ok := settings["public_key"].(string); ok {
|
||||
cfg.PublicKey = pk
|
||||
}
|
||||
}
|
||||
}
|
||||
logger.Debug(fmt.Sprintf("vk-auth: publicKey=%s", cfg.PublicKey))
|
||||
logger.Info("vk-auth: getting call preview")
|
||||
previewResp, err := httpPost("https://api.vk.ru/method/calls.getCallPreview", url.Values{
|
||||
"v": {cfg.ApiVersion},
|
||||
"vk_join_link": {joinLink},
|
||||
}, auth)
|
||||
if err == nil {
|
||||
if respObj, ok := previewResp["response"].(map[string]interface{}); ok {
|
||||
if okLink, ok := respObj["ok_join_link"].(string); ok {
|
||||
cfg.OkJoinLink = okLink
|
||||
}
|
||||
}
|
||||
}
|
||||
logger.Info("vk-auth: getting call token")
|
||||
callParams := url.Values{
|
||||
"v": {cfg.ApiVersion},
|
||||
"vk_join_link": {joinLink},
|
||||
"name": {displayName},
|
||||
}
|
||||
var callToken string
|
||||
var apiBaseURL string
|
||||
var okJoinLink string
|
||||
for attempt := 0; attempt < 5; attempt++ {
|
||||
callResp, err := httpPost("https://api.vk.ru/method/calls.getAnonymousToken", callParams, auth)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("getAnonymousToken: %w", err)
|
||||
}
|
||||
if errObj, hasErr := callResp["error"].(map[string]interface{}); hasErr {
|
||||
errCode, _ := errObj["error_code"].(float64)
|
||||
if int(errCode) == 14 {
|
||||
captchaErr := parseVKCaptchaError(errObj)
|
||||
if captchaErr == nil {
|
||||
return "", fmt.Errorf("captcha error missing fields: %v", errObj)
|
||||
}
|
||||
logger.Info("vk-auth: captcha required")
|
||||
proxyPort := StartCaptchaProxy(captchaErr.redirectURI, dialer)
|
||||
if proxyPort == 0 {
|
||||
return "", fmt.Errorf("failed to start captcha proxy")
|
||||
}
|
||||
logger.Notice(fmt.Sprintf("vk-auth: solve the captcha to continue: http://127.0.0.1:%d/", proxyPort))
|
||||
successToken := GetCaptchaResult()
|
||||
StopCaptchaProxy()
|
||||
if successToken == "" {
|
||||
return "", fmt.Errorf("captcha timed out")
|
||||
}
|
||||
logger.Info("vk-auth: captcha solved, retrying")
|
||||
captchaAttempt := captchaErr.captchaAttempt
|
||||
if captchaAttempt == "" || captchaAttempt == "0" {
|
||||
captchaAttempt = "1"
|
||||
}
|
||||
callParams = url.Values{
|
||||
"v": {cfg.ApiVersion},
|
||||
"vk_join_link": {joinLink},
|
||||
"name": {displayName},
|
||||
"captcha_key": {""},
|
||||
"captcha_sid": {captchaErr.captchaSid},
|
||||
"is_sound_captcha": {"0"},
|
||||
"success_token": {successToken},
|
||||
"captcha_ts": {captchaErr.captchaTs},
|
||||
"captcha_attempt": {captchaAttempt},
|
||||
}
|
||||
continue
|
||||
}
|
||||
return "", fmt.Errorf("VK API error: %v", errObj)
|
||||
}
|
||||
respMap, ok := callResp["response"].(map[string]interface{})
|
||||
if !ok {
|
||||
return "", fmt.Errorf("unexpected response: %v", callResp)
|
||||
}
|
||||
callToken, _ = respMap["token"].(string)
|
||||
apiBaseURL, _ = respMap["api_base_url"].(string)
|
||||
okJoinLink, _ = respMap["ok_join_link"].(string)
|
||||
break
|
||||
}
|
||||
if callToken == "" {
|
||||
return "", fmt.Errorf("failed to get call token")
|
||||
}
|
||||
logger.Info("vk-auth: authenticating with OK.ru")
|
||||
baseURL := strings.TrimRight(apiBaseURL, "/")
|
||||
if !strings.HasSuffix(baseURL, "/fb.do") {
|
||||
baseURL += "/fb.do"
|
||||
}
|
||||
deviceID := fmt.Sprintf("%d", rand.Int63n(9e18))
|
||||
sessionData, _ := json.Marshal(map[string]interface{}{
|
||||
"version": 2,
|
||||
"device_id": deviceID,
|
||||
"client_version": cfg.AppVersion,
|
||||
"client_type": "SDK_JS",
|
||||
})
|
||||
okResp, err := httpPost(baseURL, url.Values{
|
||||
"method": {"auth.anonymLogin"},
|
||||
"session_data": {string(sessionData)},
|
||||
"application_key": {cfg.PublicKey},
|
||||
"format": {"json"},
|
||||
}, nil)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("anonymLogin: %w", err)
|
||||
}
|
||||
sessionKey, _ := okResp["session_key"].(string)
|
||||
if sessionKey == "" {
|
||||
return "", fmt.Errorf("missing session_key: %v", okResp)
|
||||
}
|
||||
logger.Debug("vk-auth: OK.ru session OK")
|
||||
finalJoinLink := okJoinLink
|
||||
if finalJoinLink == "" {
|
||||
finalJoinLink = cfg.OkJoinLink
|
||||
}
|
||||
if finalJoinLink == "" {
|
||||
finalJoinLink = joinLink
|
||||
}
|
||||
result := map[string]string{
|
||||
"sessionKey": sessionKey,
|
||||
"applicationKey": cfg.PublicKey,
|
||||
"apiBaseURL": baseURL,
|
||||
"joinLink": finalJoinLink,
|
||||
"anonymToken": callToken,
|
||||
"appVersion": cfg.AppVersion,
|
||||
"protocolVersion": cfg.ProtocolVersion,
|
||||
}
|
||||
jsonBytes, _ := json.Marshal(result)
|
||||
logger.Debug("vk-auth: done")
|
||||
return string(jsonBytes), nil
|
||||
}
|
||||
|
||||
func parseVKCaptchaError(errObj map[string]interface{}) *vkCaptchaError {
|
||||
redirectURI, _ := errObj["redirect_uri"].(string)
|
||||
if redirectURI == "" {
|
||||
return nil
|
||||
}
|
||||
captchaSid := ""
|
||||
if sid, ok := errObj["captcha_sid"].(string); ok {
|
||||
captchaSid = sid
|
||||
} else if sidNum, ok := errObj["captcha_sid"].(float64); ok {
|
||||
captchaSid = fmt.Sprintf("%.0f", sidNum)
|
||||
}
|
||||
captchaTs, _ := errObj["captcha_ts"].(string)
|
||||
captchaAttempt, _ := errObj["captcha_attempt"].(string)
|
||||
return &vkCaptchaError{
|
||||
captchaSid: captchaSid,
|
||||
redirectURI: redirectURI,
|
||||
captchaTs: captchaTs,
|
||||
captchaAttempt: captchaAttempt,
|
||||
}
|
||||
}
|
||||
|
||||
func minInt(a, b int) int {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
Reference in New Issue
Block a user