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

111 lines
3.2 KiB
Go

package stripe
import (
"crypto/rand"
"encoding/json"
"fmt"
"log"
"math/big"
"os"
"path/filepath"
"strings"
"sync"
)
// BrowserFingerprint holds real browser fingerprint data harvested from a real browser.
type BrowserFingerprint struct {
CookieSupport bool `json:"cookie_support"`
DoNotTrack bool `json:"do_not_track"`
Language string `json:"language"`
Platform string `json:"platform"`
ScreenSize string `json:"screen_size"`
UserAgent string `json:"user_agent"`
Plugins string `json:"plugins"`
WebGLVendor string `json:"webgl_vendor"`
WebGLRenderer string `json:"webgl_renderer"`
FontsBits string `json:"fonts_bits"`
CanvasHash string `json:"canvas_hash"`
OaiDID string `json:"oai_did"`
CfClearance string `json:"cf_clearance"`
CsrfToken string `json:"csrf_token"`
Region string `json:"region"`
StripeFpID string `json:"stripe_fingerprint_id"`
}
// BrowserFingerprintPool manages a pool of pre-harvested browser fingerprints.
type BrowserFingerprintPool struct {
mu sync.Mutex
fingerprints []*BrowserFingerprint
index int
}
// NewBrowserFingerprintPool loads all fingerprint JSON files from a directory.
// If filterLang is not empty, only loads fingerprints matching that language prefix (e.g. "ko").
func NewBrowserFingerprintPool(dir string, filterLang ...string) (*BrowserFingerprintPool, error) {
files, err := filepath.Glob(filepath.Join(dir, "*.json"))
if err != nil {
return nil, fmt.Errorf("glob fingerprint dir: %w", err)
}
if len(files) == 0 {
return nil, fmt.Errorf("no fingerprint files found in %s", dir)
}
langPrefix := ""
if len(filterLang) > 0 && filterLang[0] != "" {
langPrefix = strings.ToLower(filterLang[0])
}
var fps []*BrowserFingerprint
for _, f := range files {
data, err := os.ReadFile(f)
if err != nil {
log.Printf("[fingerprint] warning: skip %s: %v", f, err)
continue
}
var fp BrowserFingerprint
if err := json.Unmarshal(data, &fp); err != nil {
log.Printf("[fingerprint] warning: skip %s: %v", f, err)
continue
}
if fp.CanvasHash == "" || fp.UserAgent == "" {
log.Printf("[fingerprint] warning: skip %s: missing canvas_hash or user_agent", f)
continue
}
// Filter by language if specified
if langPrefix != "" && !strings.HasPrefix(strings.ToLower(fp.Language), langPrefix) {
log.Printf("[fingerprint] skip %s: language %q doesn't match %q", filepath.Base(f), fp.Language, langPrefix)
continue
}
fps = append(fps, &fp)
}
if len(fps) == 0 {
return nil, fmt.Errorf("no valid fingerprints loaded from %s", dir)
}
// Shuffle
for i := len(fps) - 1; i > 0; i-- {
n, _ := rand.Int(rand.Reader, big.NewInt(int64(i+1)))
j := int(n.Int64())
fps[i], fps[j] = fps[j], fps[i]
}
log.Printf("[fingerprint] loaded %d browser fingerprints from %s", len(fps), dir)
return &BrowserFingerprintPool{fingerprints: fps}, nil
}
// Get returns the next fingerprint in round-robin order.
func (p *BrowserFingerprintPool) Get() *BrowserFingerprint {
p.mu.Lock()
defer p.mu.Unlock()
fp := p.fingerprints[p.index%len(p.fingerprints)]
p.index++
return fp
}
// Count returns the number of fingerprints in the pool.
func (p *BrowserFingerprintPool) Count() int {
return len(p.fingerprints)
}