修复grok 403
This commit is contained in:
@@ -1,23 +1,30 @@
|
|||||||
// Package grok implements the Grok (grok.com / xAI) provider client. Auth is the
|
// Package grok implements the Grok (grok.com / xAI) provider client. Auth is the
|
||||||
// website "sso" session cookie (a JWT whose only claim is a session_id — no exp,
|
// website "sso" session cookie (a JWT whose only claim is a session_id — no exp,
|
||||||
// no refresh: when the session dies upstream the account is simply dead, never
|
// no refresh: when the session dies upstream the account is simply dead, never
|
||||||
// renewed). grok.com gates requests with an x-statsig-id header; the web app's
|
// renewed). grok.com gates requests with an x-statsig-id header; its value
|
||||||
// value is just a base64-encoded fake JS TypeError string, which the upstream
|
// is a 70-byte anti-bot record — header[49] | counter_le32 |
|
||||||
// accepts — so we spoof it the same way (no Cloudflare clearance needed). Uses
|
// sha256("METHOD!path!counter"+suffix)[:16] | trailer — XOR-masked with one
|
||||||
// tls-client so the JA3/JA4 fingerprint matches Chrome.
|
// random byte and base64-encoded; we reproduce it exactly in statsigID (the
|
||||||
|
// build-specific header/suffix/trailer are env-overridable when grok rotates
|
||||||
|
// them). Uses tls-client so the JA3/JA4 fingerprint matches Chrome.
|
||||||
package grok
|
package grok
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/sha256"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
|
"encoding/binary"
|
||||||
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"math"
|
"math"
|
||||||
"math/rand/v2"
|
"math/rand/v2"
|
||||||
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
http "github.com/bogdanfinn/fhttp"
|
http "github.com/bogdanfinn/fhttp"
|
||||||
tlsclient "github.com/bogdanfinn/tls-client"
|
tlsclient "github.com/bogdanfinn/tls-client"
|
||||||
@@ -197,16 +204,67 @@ func (c *Client) FetchSession(ctx context.Context, token string) (email, userID
|
|||||||
return strings.TrimSpace(body.Session.Email), strings.TrimSpace(body.Session.UserID), nil
|
return strings.TrimSpace(body.Session.Email), strings.TrimSpace(body.Session.UserID), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// statsigID mirrors grok2api's _statsig_id: base64 of a fake JS TypeError string.
|
// statsig challenge constants for the current grok.com web build. They rotate
|
||||||
// The upstream's anti-bot check accepts this spoofed value.
|
// when grok ships a new build; override at runtime via env vars
|
||||||
func statsigID() string {
|
// (GROK_STATSIG_HEADER_HEX / GROK_STATSIG_SUFFIX / GROK_STATSIG_TRAILER).
|
||||||
const charset = "abcdefghijklmnopqrstuvwxyz0123456789"
|
// statsigEpoch is the challenge epoch (2023-05-01 00:00 UTC).
|
||||||
b := make([]byte, 5)
|
const (
|
||||||
for i := range b {
|
statsigEpoch = 1682924400
|
||||||
b[i] = charset[rand.IntN(len(charset))]
|
defaultStatsigHeader = "00e1ebcb2cac08f42039de1eb4d8534da581482fd09ccc95e06e3f03a3e9ddde02eb50b70c2efeaec6401f5d9b5ed329d4"
|
||||||
|
defaultStatsigSuffix = "obfiowerehiring4fa399100100"
|
||||||
|
defaultStatsigTrailer = 3
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
statsigHeader = resolveStatsigHeader()
|
||||||
|
statsigSuffix = envOr("GROK_STATSIG_SUFFIX", defaultStatsigSuffix)
|
||||||
|
statsigTrailer = resolveStatsigTrailer()
|
||||||
|
)
|
||||||
|
|
||||||
|
func resolveStatsigHeader() []byte {
|
||||||
|
h := envOr("GROK_STATSIG_HEADER_HEX", defaultStatsigHeader)
|
||||||
|
b, err := hex.DecodeString(h)
|
||||||
|
if err != nil || len(b) != 49 {
|
||||||
|
b, _ = hex.DecodeString(defaultStatsigHeader)
|
||||||
}
|
}
|
||||||
msg := fmt.Sprintf("x1:TypeError: Cannot read properties of null (reading 'children['%s']')", string(b))
|
return b
|
||||||
return base64.StdEncoding.EncodeToString([]byte(msg))
|
}
|
||||||
|
|
||||||
|
func resolveStatsigTrailer() byte {
|
||||||
|
if v := strings.TrimSpace(os.Getenv("GROK_STATSIG_TRAILER")); v != "" {
|
||||||
|
if n, err := strconv.Atoi(v); err == nil && n >= 0 && n <= 255 {
|
||||||
|
return byte(n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return defaultStatsigTrailer
|
||||||
|
}
|
||||||
|
|
||||||
|
func envOr(key, def string) string {
|
||||||
|
if v := strings.TrimSpace(os.Getenv(key)); v != "" {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
return def
|
||||||
|
}
|
||||||
|
|
||||||
|
// statsigID reproduces grok.com's x-statsig-id anti-bot token for a request. The
|
||||||
|
// token binds to the request METHOD and URL path and to a coarse timestamp, so
|
||||||
|
// it must be regenerated per request. See the package doc for the layout.
|
||||||
|
func statsigID(path, method string) string {
|
||||||
|
counter := uint32(time.Now().Unix() - statsigEpoch)
|
||||||
|
sig := fmt.Sprintf("%s!%s!%d%s", method, path, counter, statsigSuffix)
|
||||||
|
hash := sha256.Sum256([]byte(sig))
|
||||||
|
|
||||||
|
raw := make([]byte, 0, 70)
|
||||||
|
raw = append(raw, statsigHeader...)
|
||||||
|
raw = binary.LittleEndian.AppendUint32(raw, counter)
|
||||||
|
raw = append(raw, hash[:16]...)
|
||||||
|
raw = append(raw, statsigTrailer)
|
||||||
|
|
||||||
|
key := byte(rand.IntN(256))
|
||||||
|
for i := range raw {
|
||||||
|
raw[i] ^= key
|
||||||
|
}
|
||||||
|
return base64.RawStdEncoding.EncodeToString(raw)
|
||||||
}
|
}
|
||||||
|
|
||||||
// applyHeaders sets the browser-like header set + sso cookie + spoofed statsig id.
|
// applyHeaders sets the browser-like header set + sso cookie + spoofed statsig id.
|
||||||
@@ -219,7 +277,7 @@ func (c *Client) applyHeaders(req *http.Request, token string, extra map[string]
|
|||||||
"origin": {origin},
|
"origin": {origin},
|
||||||
"referer": {origin + "/"},
|
"referer": {origin + "/"},
|
||||||
"user-agent": {userAgent},
|
"user-agent": {userAgent},
|
||||||
"x-statsig-id": {statsigID()},
|
"x-statsig-id": {statsigID(req.URL.Path, req.Method)},
|
||||||
"x-xai-request-id": {uuid.NewString()},
|
"x-xai-request-id": {uuid.NewString()},
|
||||||
"sec-ch-ua": {`"Chromium";v="133", "Not(A:Brand";v="99"`},
|
"sec-ch-ua": {`"Chromium";v="133", "Not(A:Brand";v="99"`},
|
||||||
"sec-ch-ua-mobile": {"?0"},
|
"sec-ch-ua-mobile": {"?0"},
|
||||||
@@ -243,7 +301,9 @@ func (c *Client) applyHeaders(req *http.Request, token string, extra map[string]
|
|||||||
|
|
||||||
func (c *Client) newTLSClient() (tlsclient.HttpClient, error) {
|
func (c *Client) newTLSClient() (tlsclient.HttpClient, error) {
|
||||||
options := []tlsclient.HttpClientOption{
|
options := []tlsclient.HttpClientOption{
|
||||||
tlsclient.WithTimeoutSeconds(120),
|
// Video generation streams inline until progress=100; a 15s clip can take
|
||||||
|
// several minutes, so allow up to 10m (caller's genCtx caps at 12m).
|
||||||
|
tlsclient.WithTimeoutSeconds(600),
|
||||||
tlsclient.WithClientProfile(profiles.Chrome_133),
|
tlsclient.WithClientProfile(profiles.Chrome_133),
|
||||||
tlsclient.WithRandomTLSExtensionOrder(),
|
tlsclient.WithRandomTLSExtensionOrder(),
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user