9 changed files with 1414 additions and 717 deletions
@ -0,0 +1,187 @@ |
|||||
|
package main |
||||
|
|
||||
|
import ( |
||||
|
"fmt" |
||||
|
"math/rand" |
||||
|
"strings" |
||||
|
) |
||||
|
|
||||
|
var maleFirstNames = []string{ |
||||
|
"Александр", |
||||
|
"Алексей", |
||||
|
"Андрей", |
||||
|
"Антон", |
||||
|
"Арсений", |
||||
|
"Артур", |
||||
|
"Артём", |
||||
|
"Богдан", |
||||
|
"Валерий", |
||||
|
"Василий", |
||||
|
"Виктор", |
||||
|
"Владислав", |
||||
|
"Глеб", |
||||
|
"Григорий", |
||||
|
"Даниил", |
||||
|
"Денис", |
||||
|
"Дмитрий", |
||||
|
"Евгений", |
||||
|
"Егор", |
||||
|
"Иван", |
||||
|
"Игорь", |
||||
|
"Илья", |
||||
|
"Кирилл", |
||||
|
"Леонид", |
||||
|
"Максим", |
||||
|
"Марк", |
||||
|
"Матвей", |
||||
|
"Михаил", |
||||
|
"Никита", |
||||
|
"Николай", |
||||
|
"Олег", |
||||
|
"Павел", |
||||
|
"Пётр", |
||||
|
"Роман", |
||||
|
"Руслан", |
||||
|
"Сергей", |
||||
|
"Станислав", |
||||
|
"Тимофей", |
||||
|
"Фёдор", |
||||
|
} |
||||
|
|
||||
|
var femaleFirstNames = []string{ |
||||
|
"Алина", |
||||
|
"Алёна", |
||||
|
"Анастасия", |
||||
|
"Ангелина", |
||||
|
"Анна", |
||||
|
"Вера", |
||||
|
"Вероника", |
||||
|
"Виктория", |
||||
|
"Дарья", |
||||
|
"Ева", |
||||
|
"Екатерина", |
||||
|
"Елена", |
||||
|
"Елизавета", |
||||
|
"Ирина", |
||||
|
"Кира", |
||||
|
"Кристина", |
||||
|
"Ксения", |
||||
|
"Любовь", |
||||
|
"Маргарита", |
||||
|
"Марина", |
||||
|
"Мария", |
||||
|
"Милана", |
||||
|
"Надежда", |
||||
|
"Наталья", |
||||
|
"Ольга", |
||||
|
"Полина", |
||||
|
"Светлана", |
||||
|
"София", |
||||
|
"Татьяна", |
||||
|
"Юлия", |
||||
|
"Яна", |
||||
|
} |
||||
|
|
||||
|
var lastNames = []string{ |
||||
|
"Алексеев", |
||||
|
"Андреев", |
||||
|
"Антонов", |
||||
|
"Баранов", |
||||
|
"Белов", |
||||
|
"Белый", |
||||
|
"Бельский", |
||||
|
"Беляев", |
||||
|
"Борисов", |
||||
|
"Васильев", |
||||
|
"Великий", |
||||
|
"Волков", |
||||
|
"Воробьёв", |
||||
|
"Григорьев", |
||||
|
"Давыдов", |
||||
|
"Егоров", |
||||
|
"Жуков", |
||||
|
"Зайцев", |
||||
|
"Захаров", |
||||
|
"Иванов", |
||||
|
"Калинин", |
||||
|
"Ковалёв", |
||||
|
"Козлов", |
||||
|
"Комаров", |
||||
|
"Крамской", |
||||
|
"Кузнецов", |
||||
|
"Кузьмин", |
||||
|
"Лебедев", |
||||
|
"Макаров", |
||||
|
"Медведев", |
||||
|
"Михайлов", |
||||
|
"Морозов", |
||||
|
"Никитин", |
||||
|
"Николаев", |
||||
|
"Новиков", |
||||
|
"Орлов", |
||||
|
"Островский", |
||||
|
"Павлов", |
||||
|
"Петров", |
||||
|
"Покровский", |
||||
|
"Попов", |
||||
|
"Раевский", |
||||
|
"Романов", |
||||
|
"Семёнов", |
||||
|
"Сергеев", |
||||
|
"Смирнов", |
||||
|
"Соколов", |
||||
|
"Соловьёв", |
||||
|
"Степанов", |
||||
|
"Тарасов", |
||||
|
"Титов", |
||||
|
"Толстой", |
||||
|
"Трубецкой", |
||||
|
"Филиппов", |
||||
|
"Фролов", |
||||
|
"Фёдоров", |
||||
|
"Чайковский", |
||||
|
"Черный", |
||||
|
"Яковлев", |
||||
|
} |
||||
|
|
||||
|
// convertToFemaleSurname handles Russian suffix rules
|
||||
|
func convertToFemaleSurname(surname string) string { |
||||
|
// Handle adjective-style surnames:
|
||||
|
if strings.HasSuffix(surname, "ий") || strings.HasSuffix(surname, "ый") || strings.HasSuffix(surname, "ой") { |
||||
|
return surname[:len(surname)-4] + "ая" |
||||
|
} |
||||
|
|
||||
|
// Handle standard possessive surnames:
|
||||
|
if strings.HasSuffix(surname, "ов") || strings.HasSuffix(surname, "ев") || |
||||
|
strings.HasSuffix(surname, "ин") || strings.HasSuffix(surname, "ын") || |
||||
|
strings.HasSuffix(surname, "ёв") { |
||||
|
return surname + "а" |
||||
|
} |
||||
|
|
||||
|
// Foreign or unchangeable
|
||||
|
return surname |
||||
|
} |
||||
|
|
||||
|
func generateName() string { |
||||
|
// Decide gender first
|
||||
|
isFemale := rand.Intn(2) == 0 |
||||
|
|
||||
|
var fn string |
||||
|
if isFemale { |
||||
|
fn = femaleFirstNames[rand.Intn(len(femaleFirstNames))] |
||||
|
} else { |
||||
|
fn = maleFirstNames[rand.Intn(len(maleFirstNames))] |
||||
|
} |
||||
|
|
||||
|
// 70% chance to have a last name
|
||||
|
if rand.Float32() < 0.3 { |
||||
|
return fn |
||||
|
} |
||||
|
|
||||
|
ln := lastNames[rand.Intn(len(lastNames))] |
||||
|
if isFemale { |
||||
|
ln = convertToFemaleSurname(ln) |
||||
|
} |
||||
|
|
||||
|
return fmt.Sprintf("%s %s", fn, ln) |
||||
|
} |
||||
@ -0,0 +1,82 @@ |
|||||
|
package main |
||||
|
|
||||
|
import ( |
||||
|
"math/rand" |
||||
|
) |
||||
|
|
||||
|
type Profile struct { |
||||
|
UserAgent string |
||||
|
SecChUa string |
||||
|
SecChUaMobile string |
||||
|
SecChUaPlatform string |
||||
|
} |
||||
|
|
||||
|
// profiles contain paired User-Agent and Client Hints strings to harden bot detection.
|
||||
|
var profile = []Profile{ |
||||
|
// Windows Chrome
|
||||
|
{ |
||||
|
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: `"Chromium";v="146", "Not-A.Brand";v="24", "Google Chrome";v="146"`, |
||||
|
SecChUaMobile: "?0", |
||||
|
SecChUaPlatform: `"Windows"`, |
||||
|
}, |
||||
|
{ |
||||
|
UserAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36", |
||||
|
SecChUa: `"Chromium";v="145", "Not-A.Brand";v="99", "Google Chrome";v="145"`, |
||||
|
SecChUaMobile: "?0", |
||||
|
SecChUaPlatform: `"Windows"`, |
||||
|
}, |
||||
|
{ |
||||
|
UserAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36", |
||||
|
SecChUa: `"Chromium";v="144", "Not-A.Brand";v="8", "Google Chrome";v="144"`, |
||||
|
SecChUaMobile: "?0", |
||||
|
SecChUaPlatform: `"Windows"`, |
||||
|
}, |
||||
|
|
||||
|
// Windows Edge
|
||||
|
{ |
||||
|
UserAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36 Edg/146.0.0.0", |
||||
|
SecChUa: `"Chromium";v="146", "Not-A.Brand";v="24", "Microsoft Edge";v="146"`, |
||||
|
SecChUaMobile: "?0", |
||||
|
SecChUaPlatform: `"Windows"`, |
||||
|
}, |
||||
|
{ |
||||
|
UserAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36 Edg/145.0.0.0", |
||||
|
SecChUa: `"Chromium";v="145", "Not-A.Brand";v="99", "Microsoft Edge";v="145"`, |
||||
|
SecChUaMobile: "?0", |
||||
|
SecChUaPlatform: `"Windows"`, |
||||
|
}, |
||||
|
|
||||
|
// macOS Chrome
|
||||
|
{ |
||||
|
UserAgent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36", |
||||
|
SecChUa: `"Chromium";v="146", "Not-A.Brand";v="24", "Google Chrome";v="146"`, |
||||
|
SecChUaMobile: "?0", |
||||
|
SecChUaPlatform: `"macOS"`, |
||||
|
}, |
||||
|
{ |
||||
|
UserAgent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36", |
||||
|
SecChUa: `"Chromium";v="145", "Not-A.Brand";v="99", "Google Chrome";v="145"`, |
||||
|
SecChUaMobile: "?0", |
||||
|
SecChUaPlatform: `"macOS"`, |
||||
|
}, |
||||
|
|
||||
|
// Linux Chrome
|
||||
|
{ |
||||
|
UserAgent: "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36", |
||||
|
SecChUa: `"Chromium";v="146", "Not-A.Brand";v="24", "Google Chrome";v="146"`, |
||||
|
SecChUaMobile: "?0", |
||||
|
SecChUaPlatform: `"Linux"`, |
||||
|
}, |
||||
|
{ |
||||
|
UserAgent: "Mozilla/5.0 (X11; Ubuntu; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36", |
||||
|
SecChUa: `"Chromium";v="144", "Not-A.Brand";v="8", "Google Chrome";v="144"`, |
||||
|
SecChUaMobile: "?0", |
||||
|
SecChUaPlatform: `"Linux"`, |
||||
|
}, |
||||
|
} |
||||
|
|
||||
|
// getRandomProfile returns a paired User-Agent and Client Hints profile.
|
||||
|
func getRandomProfile() Profile { |
||||
|
return profile[rand.Intn(len(profile))] |
||||
|
} |
||||
@ -1,258 +0,0 @@ |
|||||
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
|
|
||||
// SPDX-License-Identifier: MIT
|
|
||||
|
|
||||
package main |
|
||||
|
|
||||
import ( |
|
||||
"bytes" |
|
||||
"context" |
|
||||
"encoding/json" |
|
||||
"fmt" |
|
||||
"io" |
|
||||
"log" |
|
||||
"math/rand" |
|
||||
"net/http" |
|
||||
"net/url" |
|
||||
"strings" |
|
||||
"time" |
|
||||
|
|
||||
"github.com/google/uuid" |
|
||||
) |
|
||||
|
|
||||
const vkClientID = "6287487" |
|
||||
const vkClientSecret = "QbYic1K3lEV5kTGiqlq2" |
|
||||
const vkAPIVersion = "5.275" |
|
||||
|
|
||||
func min(a, b int) int { |
|
||||
if a < b { |
|
||||
return a |
|
||||
} |
|
||||
return b |
|
||||
} |
|
||||
|
|
||||
// vkDelay sleeps for a random duration between minMs and maxMs to avoid bot detection
|
|
||||
func vkDelay(minMs, maxMs int) { |
|
||||
ms := minMs + rand.Intn(maxMs-minMs+1) |
|
||||
time.Sleep(time.Duration(ms) * time.Millisecond) |
|
||||
} |
|
||||
|
|
||||
func vkHTTPPost(ctx context.Context, data string, url string) (map[string]interface{}, error) { |
|
||||
client := &http.Client{ |
|
||||
Timeout: 20 * time.Second, |
|
||||
Transport: &http.Transport{ |
|
||||
MaxIdleConns: 100, |
|
||||
MaxIdleConnsPerHost: 100, |
|
||||
IdleConnTimeout: 90 * time.Second, |
|
||||
}, |
|
||||
} |
|
||||
defer client.CloseIdleConnections() |
|
||||
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer([]byte(data))) |
|
||||
if err != nil { |
|
||||
return nil, err |
|
||||
} |
|
||||
// Headers matching HAR capture exactly
|
|
||||
req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36") |
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded") |
|
||||
req.Header.Set("Accept", "*/*") |
|
||||
req.Header.Set("Accept-Language", "en-US,en;q=0.9") |
|
||||
req.Header.Set("Origin", "https://vk.ru") |
|
||||
req.Header.Set("Referer", "https://vk.ru/") |
|
||||
req.Header.Set("sec-ch-ua-platform", `"Windows"`) |
|
||||
req.Header.Set("sec-ch-ua", `"Chromium";v="146", "Not-A.Brand";v="24", "Google Chrome";v="146"`) |
|
||||
req.Header.Set("sec-ch-ua-mobile", "?0") |
|
||||
req.Header.Set("Sec-Fetch-Site", "same-site") |
|
||||
req.Header.Set("Sec-Fetch-Mode", "cors") |
|
||||
req.Header.Set("Sec-Fetch-Dest", "empty") |
|
||||
req.Header.Set("DNT", "1") |
|
||||
req.Header.Set("Priority", "u=1, i") |
|
||||
|
|
||||
httpResp, err := client.Do(req) |
|
||||
if err != nil { |
|
||||
return nil, err |
|
||||
} |
|
||||
defer httpResp.Body.Close() |
|
||||
|
|
||||
// Handle HTTP errors (redirects, rate limits, etc.)
|
|
||||
if httpResp.StatusCode >= 400 { |
|
||||
body, _ := io.ReadAll(httpResp.Body) |
|
||||
return nil, fmt.Errorf("HTTP %d from %s: %s", httpResp.StatusCode, req.URL, string(body[:min(len(body), 500)])) |
|
||||
} |
|
||||
|
|
||||
body, err := io.ReadAll(httpResp.Body) |
|
||||
if err != nil { |
|
||||
return nil, err |
|
||||
} |
|
||||
|
|
||||
// Check content type - VK may return HTML instead of JSON (captcha page, redirect, etc.)
|
|
||||
contentType := httpResp.Header.Get("Content-Type") |
|
||||
if contentType != "" && !strings.Contains(contentType, "application/json") && !strings.Contains(contentType, "text/javascript") { |
|
||||
// Log first 500 chars of non-JSON response for debugging
|
|
||||
logPreview := string(body) |
|
||||
if len(logPreview) > 500 { |
|
||||
logPreview = logPreview[:500] + "...(truncated)" |
|
||||
} |
|
||||
return nil, fmt.Errorf("unexpected content-type %s, status %d, body: %s", contentType, httpResp.StatusCode, logPreview) |
|
||||
} |
|
||||
|
|
||||
var resp map[string]interface{} |
|
||||
if err = json.Unmarshal(body, &resp); err != nil { |
|
||||
// Log the raw body for debugging
|
|
||||
logPreview := string(body) |
|
||||
if len(logPreview) > 500 { |
|
||||
logPreview = logPreview[:500] + "...(truncated)" |
|
||||
} |
|
||||
return nil, fmt.Errorf("JSON parse error: %w, body: %s", err, logPreview) |
|
||||
} |
|
||||
return resp, nil |
|
||||
} |
|
||||
|
|
||||
func getVkCreds(ctx context.Context, link string) (string, string, string, error) { |
|
||||
// Token 1 (messages)
|
|
||||
log.Println("[VK Auth] Getting Token 1...") |
|
||||
data := fmt.Sprintf("client_id=%s&token_type=messages&client_secret=%s&version=1&app_id=%s", |
|
||||
vkClientID, vkClientSecret, vkClientID) |
|
||||
resp, err := vkHTTPPost(ctx, data, "https://login.vk.ru/?act=get_anonym_token") |
|
||||
if err != nil { |
|
||||
return "", "", "", fmt.Errorf("Token 1 request error: %w", err) |
|
||||
} |
|
||||
if errMsg, ok := resp["error"].(map[string]interface{}); ok { |
|
||||
return "", "", "", fmt.Errorf("Token 1 VK error: %v", errMsg) |
|
||||
} |
|
||||
dataObj, ok := resp["data"].(map[string]interface{}) |
|
||||
if !ok { |
|
||||
return "", "", "", fmt.Errorf("invalid Token 1 response: %v", resp) |
|
||||
} |
|
||||
token1, ok := dataObj["access_token"].(string) |
|
||||
if !ok { |
|
||||
return "", "", "", fmt.Errorf("access_token not found in Token 1 response") |
|
||||
} |
|
||||
log.Println("[VK Auth] Token 1 received") |
|
||||
vkDelay(100, 200) // Token 1 → getCallPreview
|
|
||||
|
|
||||
// getCallPreview (optional, like browser)
|
|
||||
log.Println("[VK Auth] Getting call preview...") |
|
||||
cpData := fmt.Sprintf("vk_join_link=https://vk.ru/call/join/%s&fields=photo_200&access_token=%s", |
|
||||
url.QueryEscape(link), token1) |
|
||||
cpURL := fmt.Sprintf("https://api.vk.ru/method/calls.getCallPreview?v=%s&client_id=%s", vkAPIVersion, vkClientID) |
|
||||
_, _ = vkHTTPPost(ctx, cpData, cpURL) // non-critical
|
|
||||
vkDelay(500, 1000) // getCallPreview → Token 2
|
|
||||
|
|
||||
// Token 2 (may require captcha)
|
|
||||
log.Println("[VK Auth] Getting Token 2...") |
|
||||
t2Data := fmt.Sprintf("vk_join_link=https://vk.ru/call/join/%s&name=123&access_token=%s", |
|
||||
url.QueryEscape(link), token1) |
|
||||
t2URL := fmt.Sprintf("https://api.vk.ru/method/calls.getAnonymousToken?v=%s&client_id=%s", vkAPIVersion, vkClientID) |
|
||||
resp, err = vkHTTPPost(ctx, t2Data, t2URL) |
|
||||
if err != nil { |
|
||||
return "", "", "", fmt.Errorf("Token 2 request error: %w", err) |
|
||||
} |
|
||||
|
|
||||
// Check for captcha error
|
|
||||
if errMsg, ok := resp["error"].(map[string]interface{}); ok { |
|
||||
captchaErr := ParseVkCaptchaError(errMsg) |
|
||||
if captchaErr == nil || !captchaErr.IsCaptchaError() { |
|
||||
return "", "", "", fmt.Errorf("Token 2 VK error: %v", errMsg) |
|
||||
} |
|
||||
|
|
||||
log.Printf("[VK Auth] Captcha detected, solving...") |
|
||||
successToken, solveErr := SolveVkCaptcha(ctx, captchaErr) |
|
||||
if solveErr != nil { |
|
||||
return "", "", "", fmt.Errorf("captcha solving failed: %w", solveErr) |
|
||||
} |
|
||||
|
|
||||
// Delay before retry (endSession → Token 2 retry)
|
|
||||
vkDelay(100, 200) |
|
||||
|
|
||||
// Retry Token 2 with captcha solution
|
|
||||
log.Println("[VK Auth] Retrying Token 2 with captcha solution...") |
|
||||
t2Data = fmt.Sprintf( |
|
||||
"vk_join_link=https://vk.ru/call/join/%s&name=123"+ |
|
||||
"&captcha_key=&captcha_sid=%s&is_sound_captcha=0"+ |
|
||||
"&success_token=%s&captcha_ts=%s&captcha_attempt=%s"+ |
|
||||
"&access_token=%s", |
|
||||
url.QueryEscape(link), |
|
||||
captchaErr.CaptchaSid, |
|
||||
successToken, |
|
||||
captchaErr.CaptchaTs, |
|
||||
captchaErr.CaptchaAttempt, |
|
||||
token1, |
|
||||
) |
|
||||
resp, err = vkHTTPPost(ctx, t2Data, t2URL) |
|
||||
if err != nil { |
|
||||
return "", "", "", fmt.Errorf("Token 2 retry request error: %w", err) |
|
||||
} |
|
||||
if errMsg2, ok := resp["error"].(map[string]interface{}); ok { |
|
||||
return "", "", "", fmt.Errorf("Token 2 retry VK error: %v", errMsg2) |
|
||||
} |
|
||||
// Token 2 retry → Token 3
|
|
||||
vkDelay(100, 200) |
|
||||
} |
|
||||
|
|
||||
token2Obj, ok := resp["response"].(map[string]interface{}) |
|
||||
if !ok { |
|
||||
return "", "", "", fmt.Errorf("invalid Token 2 response: %v", resp) |
|
||||
} |
|
||||
token2, ok := token2Obj["token"].(string) |
|
||||
if !ok { |
|
||||
return "", "", "", fmt.Errorf("token not found in Token 2 response") |
|
||||
} |
|
||||
log.Println("[VK Auth] Token 2 received") |
|
||||
// Token 2 → Token 3
|
|
||||
vkDelay(100, 200) |
|
||||
|
|
||||
// Token 3 (OK auth.anonymLogin)
|
|
||||
log.Println("[VK Auth] Getting Token 3...") |
|
||||
sessionData := fmt.Sprintf(`{"version":2,"device_id":"%s","client_version":1.1,"client_type":"SDK_JS"}`, uuid.New()) |
|
||||
t3Data := fmt.Sprintf("session_data=%s&method=auth.anonymLogin&format=JSON&application_key=CGMMEJLGDIHBABABA", |
|
||||
url.QueryEscape(sessionData)) |
|
||||
resp, err = vkHTTPPost(ctx, t3Data, "https://calls.okcdn.ru/fb.do") |
|
||||
if err != nil { |
|
||||
return "", "", "", fmt.Errorf("Token 3 request error: %w", err) |
|
||||
} |
|
||||
if errMsg, ok := resp["error"].(string); ok && errMsg != "" { |
|
||||
return "", "", "", fmt.Errorf("Token 3 API error: %s", errMsg) |
|
||||
} |
|
||||
token3, ok := resp["session_key"].(string) |
|
||||
if !ok { |
|
||||
return "", "", "", fmt.Errorf("session_key not found in Token 3 response") |
|
||||
} |
|
||||
log.Println("[VK Auth] Token 3 received") |
|
||||
// Token 3 → Final (TURN)
|
|
||||
vkDelay(100, 200) |
|
||||
|
|
||||
// Final: vchat.joinConversationByLink (Token 4)
|
|
||||
log.Println("[VK Auth] Getting TURN credentials (Token 4)...") |
|
||||
finalData := fmt.Sprintf( |
|
||||
"joinLink=%s&isVideo=false&protocolVersion=5&capabilities=2F7F&anonymToken=%s&method=vchat.joinConversationByLink&format=JSON&application_key=CGMMEJLGDIHBABABA&session_key=%s", |
|
||||
url.QueryEscape(link), token2, token3) |
|
||||
resp, err = vkHTTPPost(ctx, finalData, "https://calls.okcdn.ru/fb.do") |
|
||||
if err != nil { |
|
||||
return "", "", "", fmt.Errorf("Final request error: %w", err) |
|
||||
} |
|
||||
if errMsg, ok := resp["error"].(string); ok && errMsg != "" { |
|
||||
return "", "", "", fmt.Errorf("Final API error: %s", errMsg) |
|
||||
} |
|
||||
|
|
||||
ts, ok := resp["turn_server"].(map[string]interface{}) |
|
||||
if !ok { |
|
||||
return "", "", "", fmt.Errorf("turn_server not found in response: %v", resp) |
|
||||
} |
|
||||
urls, _ := ts["urls"].([]interface{}) |
|
||||
if len(urls) == 0 { |
|
||||
return "", "", "", fmt.Errorf("urls not found in turn_server") |
|
||||
} |
|
||||
urlStr, _ := urls[0].(string) |
|
||||
clean := strings.Split(urlStr, "?")[0] |
|
||||
address := strings.TrimPrefix(strings.TrimPrefix(clean, "turn:"), "turns:") |
|
||||
|
|
||||
username, _ := ts["username"].(string) |
|
||||
credential, _ := ts["credential"].(string) |
|
||||
|
|
||||
if username == "" || credential == "" { |
|
||||
return "", "", "", fmt.Errorf("username or credential not found in turn_server") |
|
||||
} |
|
||||
|
|
||||
log.Println("[VK Auth] TURN credentials received") |
|
||||
vkDelay(1500, 2500) // Final delay before exit
|
|
||||
return username, credential, address, nil |
|
||||
} |
|
||||
Loading…
Reference in new issue