Files
gpt-plus-gpt/pkg/stripe/payload.go
2026-03-15 20:48:19 +08:00

190 lines
4.8 KiB
Go

package stripe
import (
"crypto/md5"
"crypto/rand"
"encoding/base64"
"encoding/json"
"fmt"
"math/big"
mrand "math/rand"
"strings"
)
// randomHex generates n bytes of cryptographically random hex.
func randomHex(nBytes int) string {
b := make([]byte, nBytes)
_, _ = rand.Read(b)
return fmt.Sprintf("%x", b)
}
// generateRandomBinaryString generates a string of random 0s and 1s.
func generateRandomBinaryString(length int) string {
var sb strings.Builder
sb.Grow(length)
for i := 0; i < length; i++ {
n, _ := rand.Int(rand.Reader, big.NewInt(2))
sb.WriteByte('0' + byte(n.Int64()))
}
return sb.String()
}
// transformFeatureValues converts extractedFeatures array to the keyed map format.
// [{v, t}, ...] -> {a: {v, t}, b: {v, t}, ...}
func transformFeatureValues(features [][]interface{}) map[string]interface{} {
result := make(map[string]interface{})
for i, f := range features {
key := string(rune('a' + i))
entry := map[string]interface{}{
"v": f[0],
"t": f[1],
}
if len(f) > 2 {
entry["at"] = f[2]
}
result[key] = entry
}
return result
}
// featuresSameLine joins all feature values with spaces (for MD5 id).
func featuresSameLine(features [][]interface{}) string {
parts := make([]string, len(features))
for i, f := range features {
parts[i] = fmt.Sprintf("%v", f[0])
}
return strings.Join(parts, " ")
}
// domainToTabTitle returns a suitable tab title for the given domain.
func domainToTabTitle(domain string) string {
switch domain {
case "chatgpt.com":
return "ChatGPT"
case "discord.com":
return "Discord | Billing | User Settings"
default:
return domain
}
}
// CreateInitPayload builds the m.stripe.com/6 fingerprint payload.
// domain should be "chatgpt.com" (the merchant site).
// If browserFP is provided, real fingerprint data overrides hardcoded defaults.
func CreateInitPayload(userAgent, domain, tagVersion string, browserFP ...*BrowserFingerprint) map[string]interface{} {
if tagVersion == "" {
tagVersion = "4.5.43"
}
// Defaults (hardcoded)
language := "en-US"
platform := "Win32"
plugins := "Browser PDF plug-in,HqVxgvf2j4FKFpUSJjZUxg368mTJr8Hq,application/pdf,pdf, aNlxBIr0,ECozZzCJECozZrdO,,ZYz, OToct9e,Ar89HqVpzhQQvAn,,tiZ, JavaScript portable-document-format plug in,7CgQIMl5k5kxBAAIjRnb05FKNGqdWTw3,application/x-google-chrome-pdf,pdf"
screenSize := "1920w_1032h_24d_1r"
canvasHash := "b723b5fba9cb9289de8b7e1e6de668fd"
fontsBits := generateRandomBinaryString(55)
cookieSupport := "true"
doNotTrack := "false"
// Override with real fingerprint if available
if len(browserFP) > 0 && browserFP[0] != nil {
fp := browserFP[0]
if fp.Language != "" {
language = fp.Language
}
if fp.Platform != "" {
platform = fp.Platform
}
if fp.Plugins != "" {
plugins = fp.Plugins
}
if fp.ScreenSize != "" {
screenSize = fp.ScreenSize
}
if fp.CanvasHash != "" {
canvasHash = fp.CanvasHash
}
if fp.FontsBits != "" {
fontsBits = fp.FontsBits
}
if fp.CookieSupport {
cookieSupport = "true"
}
if fp.DoNotTrack {
doNotTrack = "true"
}
if fp.UserAgent != "" {
userAgent = fp.UserAgent
}
}
extractedFeatures := [][]interface{}{
{cookieSupport, 0},
{doNotTrack, 0},
{language, 0},
{platform, 0},
{plugins, 19},
{screenSize, 0},
{"1", 0},
{"false", 0},
{"sessionStorage-enabled, localStorage-enabled", 3},
{fontsBits, 85},
{"", 0},
{userAgent, 0},
{"", 0},
{"false", 85, 1},
{canvasHash, 83},
}
randomVal := randomHex(10) // 20 hex chars from 10 bytes
urlToHash := fmt.Sprintf("https://%s/", domain)
hashedURL := HashURL(urlToHash)
joined := featuresSameLine(extractedFeatures)
featureID := fmt.Sprintf("%x", md5.Sum([]byte(joined)))
tabTitle := domainToTabTitle(domain)
// Random timing values matching JS: Math.floor(Math.random() * (350 - 200 + 1) + 200)
t := mrand.Intn(151) + 200 // 200-350
n := mrand.Intn(251) + 100 // 100-350
return map[string]interface{}{
"v2": 1,
"id": featureID,
"t": t,
"tag": tagVersion,
"src": "js",
"a": transformFeatureValues(extractedFeatures),
"b": map[string]interface{}{
"a": hashedURL,
"b": hashedURL,
"c": SHA256WithSalt(tabTitle),
"d": "NA",
"e": "NA",
"f": false,
"g": true,
"h": true,
"i": []string{"location"},
"j": []interface{}{},
"n": n,
"u": domain,
"v": domain,
"w": GetHashTimestampWithSalt(randomVal),
},
"h": randomVal,
}
}
// EncodePayload JSON-encodes, encodeURIComponent-encodes, then base64-encodes the payload.
// Matches JS: Buffer.from(encodeURIComponent(JSON.stringify(payload))).toString('base64')
func EncodePayload(payload map[string]interface{}) (string, error) {
jsonBytes, err := json.Marshal(payload)
if err != nil {
return "", fmt.Errorf("marshal payload: %w", err)
}
encoded := EncodeURIComponent(string(jsonBytes))
return base64.StdEncoding.EncodeToString([]byte(encoded)), nil
}