dvc890's picture
Upload 42 files
581b6d4 verified
package funcaptcha
import (
"encoding/json"
"errors"
"fmt"
"io"
"log"
"math/rand"
"strconv"
"strings"
"time"
http "github.com/bogdanfinn/fhttp"
tls_client "github.com/bogdanfinn/tls-client"
"github.com/bogdanfinn/tls-client/profiles"
)
type Session struct {
Sid string `json:"sid"`
SessionToken string `json:"session_token"`
Hex string `json:"hex"`
ChallengeLogger challengeLogger `json:"challenge_logger"`
Challenge Challenge `json:"challenge"`
ConciseChallenge ConciseChallenge `json:"concise_challenge"`
Headers http.Header `json:"headers"`
Client *tls_client.HttpClient `json:"-"`
options []tls_client.HttpClientOption `json:"-"`
}
type ConciseChallenge struct {
GameType string `json:"game_type"`
URLs []string `json:"urls"`
Instructions string `json:"instructions"`
}
type Input struct {
Index int
}
type ValueFunc func(Input) Input
type KeyFunc func(Input) interface{}
var Yz = map[int]struct {
Value map[string]ValueFunc
Key map[string]KeyFunc
}{
4: {
Value: map[string]ValueFunc{
"alpha": func(c Input) Input {
yValueStr := strconv.Itoa(c.Index) // 转换为字符串
combinedStr := yValueStr + strconv.Itoa(1) // 加1
combinedInt, _ := strconv.Atoi(combinedStr) // 将合并后的字符串转回为整数
return Input{Index: combinedInt - 2}
},
"beta": func(c Input) Input { return Input{Index: -c.Index} },
"gamma": func(c Input) Input { return Input{Index: 3 * (3 - c.Index)} },
"delta": func(c Input) Input { return Input{Index: 7 * c.Index} },
"epsilon": func(c Input) Input { return Input{Index: 2 * c.Index} },
"zeta": func(c Input) Input {
if c.Index != 0 {
return Input{Index: 100 / c.Index}
}
return Input{Index: c.Index}
},
},
Key: map[string]KeyFunc{
"alpha": func(c Input) interface{} {
return []int{rand.Intn(100), c.Index, rand.Intn(100)}
},
"beta": func(c Input) interface{} {
return map[string]int{
"size": 50 - c.Index,
"id": c.Index,
"limit": 10 * c.Index,
"req_timestamp": int(time.Now().UnixNano() / int64(time.Millisecond)),
}
},
"gamma": func(c Input) interface{} {
return c.Index
},
"delta": func(c Input) interface{} {
return map[string]int{"index": c.Index}
},
"epsilon": func(c Input) interface{} {
arr := make([]int, rand.Intn(5)+1)
randIndex := rand.Intn(len(arr))
for i := range arr {
if i == randIndex {
arr[i] = c.Index
} else {
arr[i] = rand.Intn(10)
}
}
return append(arr, randIndex)
},
"zeta": func(c Input) interface{} {
return append(make([]int, rand.Intn(5)+1), c.Index)
},
},
},
}
func YB(gameType int, apiBreaker *ApiBreaker) func(Input) interface{} {
return func(input Input) interface{} {
for _, valueFuncName := range apiBreaker.Value {
input = Yz[gameType].Value[valueFuncName](input)
}
return Yz[gameType].Key[apiBreaker.Key](input)
}
}
type Challenge struct {
SessionToken string `json:"session_token"`
ChallengeID string `json:"challengeID"`
ChallengeURL string `json:"challengeURL"`
AudioChallengeURLs []string `json:"audio_challenge_urls"`
AudioGameRateLimited interface{} `json:"audio_game_rate_limited"`
Sec int `json:"sec"`
EndURL interface{} `json:"end_url"`
GameData struct {
GameType int `json:"gameType"`
GameVariant string `json:"game_variant"`
InstructionString string `json:"instruction_string"`
CustomGUI struct {
ChallengeIMGs []string `json:"_challenge_imgs"`
ApiBreaker *ApiBreaker `json:"api_breaker"`
ApiBreakerV2Enabled int `json:"api_breaker_v2_enabled"`
} `json:"customGUI"`
} `json:"game_data"`
GameSID string `json:"game_sid"`
SID string `json:"sid"`
Lang string `json:"lang"`
StringTablePrefixes []interface{} `json:"string_table_prefixes"`
StringTable map[string]string `json:"string_table"`
EarlyVictoryMessage interface{} `json:"earlyVictoryMessage"`
FontSizeAdjustments interface{} `json:"font_size_adjustments"`
StyleTheme string `json:"style_theme"`
}
type challengeLogger struct {
Sid string `json:"sid"`
SessionToken string `json:"session_token"`
AnalyticsTier int `json:"analytics_tier"`
RenderType string `json:"render_type"`
Category string `json:"category"`
Action string `json:"action"`
// Omit if empty
GameToken string `json:"game_token,omitempty"`
GameType string `json:"game_type,omitempty"`
}
type requestChallenge struct {
Sid string `json:"sid"`
Token string `json:"token"`
AnalyticsTier int `json:"analytics_tier"`
RenderType string `json:"render_type"`
Lang string `json:"lang"`
IsAudioGame bool `json:"isAudioGame"`
APIBreakerVersion string `json:"apiBreakerVersion"`
}
type submitChallenge struct {
SessionToken string `json:"session_token"`
Sid string `json:"sid"`
GameToken string `json:"game_token"`
Guess string `json:"guess"`
RenderType string `json:"render_type"`
AnalyticsTier int `json:"analytics_tier"`
Bio string `json:"bio"`
}
type ApiBreaker struct {
Key string `json:"key"`
Value []string `json:"value"`
}
func StartChallenge(full_session, hex string) (*Session, error) {
fields := strings.Split(full_session, "|")
session_token := fields[0]
sid := strings.Split(fields[1], "=")[1]
session := Session{
Sid: sid,
SessionToken: session_token,
Hex: hex,
}
session.Headers = headers
session.Headers.Set("Referer", fmt.Sprintf("https://client-api.arkoselabs.com/fc/assets/ec-game-core/game-core/1.15.0/standard/index.html?session=%s", strings.Replace(full_session, "|", "&", -1)))
session.ChallengeLogger = challengeLogger{
Sid: sid,
SessionToken: session_token,
AnalyticsTier: 40,
RenderType: "canvas",
}
err := session.log("", 0, "Site URL", fmt.Sprintf("https://client-api.arkoselabs.com/v2/1.5.5/enforcement.%s.html", hex))
jar := tls_client.NewCookieJar()
session.options = []tls_client.HttpClientOption{
tls_client.WithTimeoutSeconds(360),
tls_client.WithClientProfile(profiles.Chrome_117),
tls_client.WithRandomTLSExtensionOrder(),
tls_client.WithNotFollowRedirects(),
tls_client.WithCookieJar(jar),
}
client, _ := tls_client.NewHttpClient(tls_client.NewNoopLogger(), session.options...)
session.Client = &client
return &session, err
}
func (c *Session) RequestChallenge(isAudioGame bool) (*ApiBreaker, error) {
challenge_request := requestChallenge{
Sid: c.Sid,
Token: c.SessionToken,
AnalyticsTier: 40,
RenderType: "canvas",
Lang: "en-us",
IsAudioGame: isAudioGame,
APIBreakerVersion: "green",
}
payload := jsonToForm(toJSON(challenge_request))
req, _ := http.NewRequest(http.MethodPost, "https://client-api.arkoselabs.com/fc/gfct/", strings.NewReader(payload))
req.Header = c.Headers
req.Header.Set("X-NewRelic-Timestamp", getTimeStamp())
resp, err := (*c.Client).Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return nil, fmt.Errorf("status code %d", resp.StatusCode)
}
body, _ := io.ReadAll(resp.Body)
var challenge_data Challenge
err = json.Unmarshal(body, &challenge_data)
if err != nil {
return nil, err
}
err = c.log(challenge_data.ChallengeID, challenge_data.GameData.GameType, "loaded", "game loaded")
c.Challenge = challenge_data
// Build concise challenge
var challenge_type string
var challenge_urls []string
var key string
var apiBreaker *ApiBreaker
switch challenge_data.GameData.GameType {
case 4:
challenge_type = "image"
challenge_urls = challenge_data.GameData.CustomGUI.ChallengeIMGs
instruction_string := challenge_data.GameData.InstructionString
key = fmt.Sprintf("4.instructions-%s", instruction_string)
if challenge_data.GameData.CustomGUI.ApiBreakerV2Enabled == 1 {
apiBreaker = challenge_data.GameData.CustomGUI.ApiBreaker
}
case 101:
challenge_type = "audio"
challenge_urls = challenge_data.AudioChallengeURLs
instruction_string := challenge_data.GameData.GameVariant
key = fmt.Sprintf("audio_game.instructions-%s", instruction_string)
default:
challenge_type = "unknown"
challenge_urls = []string{}
}
c.ConciseChallenge = ConciseChallenge{
GameType: challenge_type,
URLs: challenge_urls,
Instructions: strings.ReplaceAll(strings.ReplaceAll(challenge_data.StringTable[key], "<strong>", ""), "</strong>", ""),
}
return apiBreaker, err
}
func (c *Session) SubmitAnswer(indices []int, isAudio bool, apiBreaker *ApiBreaker) error {
submission := submitChallenge{
SessionToken: c.SessionToken,
Sid: c.Sid,
GameToken: c.Challenge.ChallengeID,
RenderType: "canvas",
AnalyticsTier: 40,
Bio: "eyJtYmlvIjoiMTUwLDAsMTE3LDIzOTszMDAsMCwxMjEsMjIxOzMxNywwLDEyNCwyMTY7NTUwLDAsMTI5LDIxMDs1NjcsMCwxMzQsMjA3OzYxNywwLDE0NCwyMDU7NjUwLDAsMTU1LDIwNTs2NjcsMCwxNjUsMjA1OzY4NCwwLDE3MywyMDc7NzAwLDAsMTc4LDIxMjs4MzQsMCwyMjEsMjI4OzI2MDY3LDAsMTkzLDM1MTsyNjEwMSwwLDE4NSwzNTM7MjYxMDEsMCwxODAsMzU3OzI2MTM0LDAsMTcyLDM2MTsyNjE4NCwwLDE2NywzNjM7MjYyMTcsMCwxNjEsMzY1OzI2MzM0LDAsMTU2LDM2NDsyNjM1MSwwLDE1MiwzNTQ7MjYzNjcsMCwxNTIsMzQzOzI2Mzg0LDAsMTUyLDMzMTsyNjQ2NywwLDE1MSwzMjU7MjY0NjcsMCwxNTEsMzE3OzI2NTAxLDAsMTQ5LDMxMTsyNjY4NCwxLDE0NywzMDc7MjY3NTEsMiwxNDcsMzA3OzMwNDUxLDAsMzcsNDM3OzMwNDY4LDAsNTcsNDI0OzMwNDg0LDAsNjYsNDE0OzMwNTAxLDAsODgsMzkwOzMwNTAxLDAsMTA0LDM2OTszMDUxOCwwLDEyMSwzNDk7MzA1MzQsMCwxNDEsMzI0OzMwNTUxLDAsMTQ5LDMxNDszMDU4NCwwLDE1MywzMDQ7MzA2MTgsMCwxNTUsMjk2OzMwNzUxLDAsMTU5LDI4OTszMDc2OCwwLDE2NywyODA7MzA3ODQsMCwxNzcsMjc0OzMwODE4LDAsMTgzLDI3MDszMDg1MSwwLDE5MSwyNzA7MzA4ODQsMCwyMDEsMjY4OzMwOTE4LDAsMjA4LDI2ODszMTIzNCwwLDIwNCwyNjM7MzEyNTEsMCwyMDAsMjU3OzMxMzg0LDAsMTk1LDI1MTszMTQxOCwwLDE4OSwyNDk7MzE1NTEsMSwxODksMjQ5OzMxNjM0LDIsMTg5LDI0OTszMTcxOCwxLDE4OSwyNDk7MzE3ODQsMiwxODksMjQ5OzMxODg0LDEsMTg5LDI0OTszMTk2OCwyLDE4OSwyNDk7MzIyODQsMCwyMDIsMjQ5OzMyMzE4LDAsMjE2LDI0NzszMjMxOCwwLDIzNCwyNDU7MzIzMzQsMCwyNjksMjQ1OzMyMzUxLDAsMzAwLDI0NTszMjM2OCwwLDMzOSwyNDE7MzIzODQsMCwzODgsMjM5OzMyNjE4LDAsMzkwLDI0NzszMjYzNCwwLDM3NCwyNTM7MzI2NTEsMCwzNjUsMjU1OzMyNjY4LDAsMzUzLDI1NzszMjk1MSwxLDM0OCwyNTc7MzMwMDEsMiwzNDgsMjU3OzMzNTY4LDAsMzI4LDI3MjszMzU4NCwwLDMxOSwyNzg7MzM2MDEsMCwzMDcsMjg2OzMzNjUxLDAsMjk1LDI5NjszMzY1MSwwLDI5MSwzMDA7MzM2ODQsMCwyODEsMzA5OzMzNjg0LDAsMjcyLDMxNTszMzcxOCwwLDI2NiwzMTc7MzM3MzQsMCwyNTgsMzIzOzMzNzUxLDAsMjUyLDMyNzszMzc1MSwwLDI0NiwzMzM7MzM3NjgsMCwyNDAsMzM3OzMzNzg0LDAsMjM2LDM0MTszMzgxOCwwLDIyNywzNDc7MzM4MzQsMCwyMjEsMzUzOzM0MDUxLDAsMjE2LDM1NDszNDA2OCwwLDIxMCwzNDg7MzQwODQsMCwyMDQsMzQ0OzM0MTAxLDAsMTk4LDM0MDszNDEzNCwwLDE5NCwzMzY7MzQ1ODQsMSwxOTIsMzM0OzM0NjUxLDIsMTkyLDMzNDsiLCJ0YmlvIjoiIiwia2JpbyI6IiJ9",
}
var answerIndex []string
if isAudio {
for _, answer := range indices {
answerIndex = append(answerIndex, strconv.Itoa(answer))
}
} else {
for _, answer := range indices {
input := Input{Index: answer}
encoder := YB(4, apiBreaker)
result := encoder(input)
marshal, _ := json.Marshal(result)
answerIndex = append(answerIndex, string(marshal))
}
}
answer := "[" + strings.Join(answerIndex, ",") + "]"
submission.Guess = Encrypt(answer, c.SessionToken)
payload := jsonToForm(toJSON(submission))
req, _ := http.NewRequest(http.MethodPost, "https://client-api.arkoselabs.com/fc/ca/", strings.NewReader(payload))
req.Header = c.Headers
req.Header.Set("X-Requested-ID", getRequestId(c.SessionToken))
req.Header.Set("X-NewRelic-Timestamp", getTimeStamp())
resp, err := (*c.Client).Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
var response struct {
Error string `json:"error"`
Response string `json:"response"`
Solved bool `json:"solved"`
IncorrectGuess string `json:"incorrect_guess"`
Score int `json:"score"`
}
log.Println(string(body))
err = json.Unmarshal(body, &response)
if err != nil {
return err
}
if response.Error != "" {
return errors.New(response.Error)
}
if !response.Solved {
return fmt.Errorf("incorrect guess: %s", response.IncorrectGuess)
}
// Set new client
cli, _ := tls_client.NewHttpClient(tls_client.NewNoopLogger(), c.options...)
c.Client = &cli
return nil
}
func (c *Session) log(game_token string, game_type int, category, action string) error {
v := c.ChallengeLogger
v.GameToken = game_token
if game_type != 0 {
v.GameType = fmt.Sprintf("%d", game_type)
}
v.Category = category
v.Action = action
request, _ := http.NewRequest(http.MethodPost, "https://client-api.arkoselabs.com/fc/a/", strings.NewReader(jsonToForm(toJSON(v))))
request.Header = headers
resp, err := (*c.Client).Do(request)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return fmt.Errorf("status code %d", resp.StatusCode)
}
return nil
}