diff --git a/.gitignore b/.gitignore index 60e0dc641..8a0fdc77f 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ tools/authenticator/access_tokens.txt *.txt aurora chatgpttoapi +chatgpt2api tools/authenticator/.proxies.txt.swp .env *.har @@ -16,4 +17,6 @@ tools/authenticator/.proxies.txt.swp .claude .gocache .atomcode -CLAUDE.md \ No newline at end of file +CLAUDE.md +MIGRATION_PLAN.md +_scratch/ \ No newline at end of file diff --git a/VERSION b/VERSION index cc6612c36..a6254504e 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.3.0 \ No newline at end of file +2.3.1 \ No newline at end of file diff --git a/conversion/requests/chatgpt/convert.go b/conversion/requests/chatgpt/convert.go index b5300babc..9763ed81e 100644 --- a/conversion/requests/chatgpt/convert.go +++ b/conversion/requests/chatgpt/convert.go @@ -5,10 +5,14 @@ import ( "aurora/internal/tokens" chatgpt_types "aurora/typings/chatgpt" official_types "aurora/typings/official" + "aurora/httpclient" + "encoding/base64" + "io" + "net/http" "strings" ) -func ConvertAPIRequest(api_request official_types.APIRequest, secret *tokens.Secret, proxy string) chatgpt_types.ChatGPTRequest { +func ConvertAPIRequest(api_request official_types.APIRequest, secret *tokens.Secret, proxy string, client httpclient.AuroraHttpClient) chatgpt_types.ChatGPTRequest { chatgpt_request := chatgpt_types.NewChatGPTRequest() // Model is passed directly to upstream; default to "auto" if not provided @@ -22,7 +26,7 @@ func ConvertAPIRequest(api_request official_types.APIRequest, secret *tokens.Sec if apiMessage.Role == "system" { apiMessage.Role = "critic" } - parts, metadata := buildMessageParts(apiMessage) + parts, metadata := buildMessageParts(apiMessage, client, secret, proxy) if len(metadata) > 0 { chatgpt_request.AddMultimodalMessage(apiMessage.Role, parts, metadata) continue @@ -39,9 +43,9 @@ func ConvertTTSAPIRequest(input string) chatgpt_types.ChatGPTRequest { return chatgpt_request } -func buildMessageParts(message official_types.APIMessage) ([]interface{}, map[string]interface{}) { +func buildMessageParts(message official_types.APIMessage, client httpclient.AuroraHttpClient, secret *tokens.Secret, proxy string) ([]interface{}, map[string]interface{}) { text := message.Text() - files := enrichFiles(message.Files()) + files := enrichFiles(message.Files(), client, secret, proxy) if len(files) == 0 { return []interface{}{text}, nil } @@ -106,7 +110,7 @@ func buildMessageParts(message official_types.APIMessage) ([]interface{}, map[st } } -func enrichFiles(files []official_types.FileAttachment) []official_types.FileAttachment { +func enrichFiles(files []official_types.FileAttachment, client httpclient.AuroraHttpClient, secret *tokens.Secret, proxy string) []official_types.FileAttachment { enriched := make([]official_types.FileAttachment, 0, len(files)) seen := make(map[string]bool) for _, file := range files { @@ -114,7 +118,18 @@ func enrichFiles(files []official_types.FileAttachment) []official_types.FileAtt if id == "" || seen[id] { continue } - if uploaded, ok := backendchatgpt.LookupUploadedFile(id); ok { + + // 处理 image_url 的 inline 数据(data: URL 或 http URL) + if file.Source != "" && client != nil && secret != nil { + if uploaded, ok := uploadInlineImage(file, client, secret, proxy); ok { + file = uploaded + } else { + // 免费账号或上传失败:丢弃图片,只保留文本 + continue + } + } + + if uploaded, ok := backendchatgpt.LookupUploadedFile(fileID(file)); ok { if file.ID == "" { file.ID = uploaded.ID } @@ -137,12 +152,109 @@ func enrichFiles(files []official_types.FileAttachment) []official_types.FileAtt file.LibraryFileID = uploaded.LibraryFileID } } - seen[id] = true + seen[fileID(file)] = true enriched = append(enriched, file) } return enriched } +// uploadInlineImage 将 data: URL 或 http URL 图片上传到 ChatGPT 文件服务。 +func uploadInlineImage(file official_types.FileAttachment, client httpclient.AuroraHttpClient, secret *tokens.Secret, proxy string) (official_types.FileAttachment, bool) { + src := file.Source + var data []byte + var filename string + var contentType string + + if strings.HasPrefix(src, "data:") { + // data:image/png;base64,iVBOR... + commaIdx := strings.Index(src, ",") + if commaIdx < 0 { + return file, false + } + meta := src[:commaIdx] + b64data := src[commaIdx+1:] + // 提取 mime type + if semiIdx := strings.Index(meta, ";"); semiIdx > 5 { + contentType = meta[5:semiIdx] + } + var err error + data, err = base64.StdEncoding.DecodeString(b64data) + if err != nil { + // 尝试 raw base64 + data, err = base64.RawStdEncoding.DecodeString(b64data) + if err != nil { + return file, false + } + } + filename = "image.png" + if contentType == "" { + contentType = "image/png" + } + } else if strings.HasPrefix(src, "http://") || strings.HasPrefix(src, "https://") { + // 下载远程图片 + resp, err := http.Get(src) + if err != nil { + return file, false + } + defer resp.Body.Close() + if resp.StatusCode != 200 { + return file, false + } + data, err = io.ReadAll(resp.Body) + if err != nil { + return file, false + } + contentType = resp.Header.Get("Content-Type") + filename = guessFilenameFromURL(src) + } else { + return file, false + } + + if len(data) == 0 { + return file, false + } + if contentType == "" { + contentType = http.DetectContentType(data) + } + if filename == "" { + filename = "image.png" + } + + uploaded, _, err := backendchatgpt.UploadFile(client, secret, proxy, filename, contentType, data) + if err != nil { + // 免费 token 无法上传文件,回退:把 data URL 原样传递 + return file, false + } + + return official_types.FileAttachment{ + ID: uploaded.FileID, + FileID: uploaded.FileID, + Name: uploaded.Filename, + FileName: uploaded.Filename, + Filename: uploaded.Filename, + MimeType: uploaded.MimeType, + MIMEType: uploaded.MimeType, + Size: uploaded.Bytes, + Width: uploaded.Width, + Height: uploaded.Height, + LibraryFileID: uploaded.LibraryFileID, + }, true +} + +func guessFilenameFromURL(url string) string { + idx := strings.LastIndex(url, "/") + if idx >= 0 && idx < len(url)-1 { + name := url[idx+1:] + if q := strings.Index(name, "?"); q >= 0 { + name = name[:q] + } + if name != "" { + return name + } + } + return "image.png" +} + func fileID(file official_types.FileAttachment) string { if strings.TrimSpace(file.FileID) != "" { return strings.TrimSpace(file.FileID) diff --git a/initialize/handlers.go b/initialize/handlers.go index 3e1da5eae..b26e13fa7 100644 --- a/initialize/handlers.go +++ b/initialize/handlers.go @@ -286,7 +286,7 @@ func (h *Handler) nightmare(c *gin.Context) { client := bogdanfinn.NewStdClient() // Convert the chat request to a ChatGPT request - translated_request := chatgptrequestconverter.ConvertAPIRequest(original_request, secret, proxyUrl) + translated_request := chatgptrequestconverter.ConvertAPIRequest(original_request, secret, proxyUrl, client) clientState := chatgpt.NewChatClientState() clientState.ConversationID = translated_request.ConversationID clientState.ParentMessageID = translated_request.ParentMessageID @@ -438,7 +438,7 @@ func (h *Handler) responses(c *gin.Context) { uid := uuid.NewString() client := bogdanfinn.NewStdClient() - translated_request := chatgptrequestconverter.ConvertAPIRequest(original_request, secret, proxyUrl) + translated_request := chatgptrequestconverter.ConvertAPIRequest(original_request, secret, proxyUrl, client) clientState := chatgpt.NewChatClientState() clientState.ConversationID = translated_request.ConversationID clientState.ParentMessageID = translated_request.ParentMessageID @@ -726,7 +726,7 @@ func (h *Handler) files(c *gin.Context) { contentType := formFile.Header.Get("Content-Type") client := bogdanfinn.NewStdClient() client.SetCookies("https://chatgpt.com", chatgpt.BasicCookies) - uploaded, status, err := chatgpt.UploadFile(client, secret, h.proxy.GetProxyIP(), formFile.Filename, contentType, c.PostForm("purpose"), data) + uploaded, status, err := chatgpt.UploadFile(client, secret, h.proxy.GetProxyIP(), formFile.Filename, contentType, data) if err != nil { c.JSON(status, gin.H{"error": gin.H{ "message": err.Error(), diff --git a/internal/chatgpt/files.go b/internal/chatgpt/files.go index 387a8d8dd..b30aeb9b9 100644 --- a/internal/chatgpt/files.go +++ b/internal/chatgpt/files.go @@ -6,10 +6,12 @@ import ( "bytes" "encoding/json" "fmt" + "image" + _ "image/gif" + _ "image/jpeg" + _ "image/png" "io" - "mime" "net/http" - "path/filepath" "strings" "sync" ) @@ -23,6 +25,8 @@ type UploadedFile struct { Filename string `json:"filename,omitempty"` Purpose string `json:"purpose,omitempty"` MimeType string `json:"mime_type,omitempty"` + Width int `json:"width,omitempty"` + Height int `json:"height,omitempty"` LibraryFileID string `json:"library_file_id,omitempty"` } @@ -56,34 +60,61 @@ func LookupUploadedFile(fileID string) (UploadedFile, bool) { return file, ok } -func UploadFile(client httpclient.AuroraHttpClient, secret *tokens.Secret, proxy, filename, contentType, purpose string, data []byte) (UploadedFile, int, error) { +// UploadFile 执行完整三步上传,对齐 chatgpttoapi/files.go UploadFile。 +func UploadFile(client httpclient.AuroraHttpClient, secret *tokens.Secret, proxy, filename, mimeHint string, data []byte) (UploadedFile, int, error) { if proxy != "" { client.SetProxy(proxy) } if secret == nil || secret.Token == "" || secret.IsFree { return UploadedFile{}, http.StatusBadRequest, fmt.Errorf("file upload requires a logged-in ChatGPT access token") } + if len(data) == 0 { + return UploadedFile{}, http.StatusBadRequest, fmt.Errorf("empty file data") + } + + mime, ext := resolveMime(data, mimeHint) + useCase := "multimodal" + if !strings.HasPrefix(mime, "image/") { + useCase = "my_files" + } filename = strings.TrimSpace(filename) if filename == "" { - filename = "upload.bin" + filename = fmt.Sprintf("file-%d%s", len(data), ext) } - if contentType == "" { - contentType = mime.TypeByExtension(strings.ToLower(filepath.Ext(filename))) + + var width, height int + if strings.HasPrefix(mime, "image/") { + if cfg, _, err := image.DecodeConfig(bytes.NewReader(data)); err == nil { + width = cfg.Width + height = cfg.Height + } } - if contentType == "" { - contentType = http.DetectContentType(data) + + // ---- Step 1: POST /backend-api/files ---- + step1Payload := map[string]interface{}{ + "file_name": filename, + "file_size": len(data), + "use_case": useCase, + "mime_type": mime, + "store_in_library": true, + "library_persistence_mode": "opportunistic", } - if purpose == "" { - purpose = "assistants" + if width > 0 && height > 0 { + step1Payload["width"] = width + step1Payload["height"] = height } - meta, status, err := createUpload(client, secret, filename, len(data)) + meta, status, err := createUpload(client, secret, step1Payload) if err != nil { return UploadedFile{}, status, err } - if status, err := putUpload(client, meta.UploadURL, contentType, data); err != nil { + + // ---- Step 2: PUT upload_url (Azure Blob) ---- + if status, err := putUpload(client, meta.UploadURL, mime, data); err != nil { return UploadedFile{}, status, err } + + // ---- Step 3: POST /backend-api/files/{file_id}/uploaded ---- if status, err := confirmUpload(client, secret, meta.FileID); err != nil { return UploadedFile{}, status, err } @@ -94,24 +125,16 @@ func UploadFile(client httpclient.AuroraHttpClient, secret *tokens.Secret, proxy Object: "file", Bytes: int64(len(data)), Filename: filename, - Purpose: purpose, - MimeType: contentType, + MimeType: mime, + Width: width, + Height: height, LibraryFileID: meta.LibraryFileID, } RegisterUploadedFile(result) return result, http.StatusOK, nil } -func createUpload(client httpclient.AuroraHttpClient, secret *tokens.Secret, filename string, size int) (uploadMetaResponse, int, error) { - payload := map[string]interface{}{ - "file_name": filename, - "file_size": size, - "use_case": "multimodal", - "timezone_offset_min": -480, - "reset_rate_limits": false, - "store_in_library": true, - "library_persistence_mode": "opportunistic", - } +func createUpload(client httpclient.AuroraHttpClient, secret *tokens.Secret, payload map[string]interface{}) (uploadMetaResponse, int, error) { body, err := json.Marshal(payload) if err != nil { return uploadMetaResponse{}, http.StatusInternalServerError, err @@ -189,3 +212,61 @@ func confirmUpload(client httpclient.AuroraHttpClient, secret *tokens.Secret, fi } return response.StatusCode, nil } + +// ── MIME 检测(对齐 chatgpttoapi/files.go) ── + +func resolveMime(data []byte, mimeHint string) (mime, ext string) { + sniffed, sniffExt := sniffMime(data) + hint := normalizeMime(mimeHint) + if hint != "" && hint != "application/octet-stream" { + ext = extFromMime(hint) + if ext == "" { + ext = sniffExt + } + return hint, ext + } + return sniffed, sniffExt +} + +func normalizeMime(s string) string { + s = strings.TrimSpace(s) + if i := strings.Index(s, ";"); i >= 0 { + s = strings.TrimSpace(s[:i]) + } + return s +} + +func sniffMime(data []byte) (mime, ext string) { + n := 512 + if len(data) < n { + n = len(data) + } + mime = http.DetectContentType(data[:n]) + ext = extFromMime(mime) + return mime, ext +} + +func extFromMime(m string) string { + switch strings.ToLower(normalizeMime(m)) { + case "image/jpeg": + return ".jpg" + case "image/png": + return ".png" + case "image/gif": + return ".gif" + case "image/webp": + return ".webp" + case "application/pdf": + return ".pdf" + case "text/plain": + return ".txt" + case "text/csv": + return ".csv" + case "application/json": + return ".json" + case "text/markdown": + return ".md" + default: + return "" + } +} diff --git a/internal/chatgpt/request.go b/internal/chatgpt/request.go index 21acf85bb..8f8b03f4b 100644 --- a/internal/chatgpt/request.go +++ b/internal/chatgpt/request.go @@ -5,6 +5,7 @@ import ( "aurora/httpclient" "aurora/internal/prooftoken" "aurora/internal/tokens" + "aurora/internal/turnstile" "aurora/typings" chatgpt_types "aurora/typings/chatgpt" official_types "aurora/typings/official" @@ -141,7 +142,7 @@ func CalcProofToken(require *ChatRequire, state *ChatClientState) string { if state != nil && state.UserAgent != "" { ua = state.UserAgent } - return prooftoken.SolveProofToken(require.Proof.Seed, require.Proof.Difficulty, ua) + return prooftoken.SolveProofToken(require.Proof.Seed, require.Proof.Difficulty, ua, cachedScripts, cachedDpl) } type ChatRequire struct { @@ -182,7 +183,7 @@ func InitSentinelWithState(client httpclient.AuroraHttpClient, secret *tokens.Se if state != nil && state.UserAgent != "" { ua = state.UserAgent } - requirementsToken := prooftoken.NewPOWConfig(ua).RequirementsToken() + requirementsToken := prooftoken.NewPOWConfig(ua, cachedScripts, cachedDpl).RequirementsToken() prepare, status, err := POSTSentinelPrepareWithState(client, secret, requirementsToken, state) if err != nil { if secret.IsFree && status == http.StatusUnauthorized && retry < 2 { @@ -216,9 +217,9 @@ func InitSentinelWithState(client httpclient.AuroraHttpClient, secret *tokens.Se } var turnstileToken string if prepare.Turnstile.DX != "" { - turnstileToken = prooftoken.Solve(prepare.Turnstile.DX, requirementsToken) + turnstileToken = turnstile.SolveWithScripts(prepare.Turnstile.DX, requirementsToken, cachedScripts) if turnstileToken == "" { - turnstileToken = prooftoken.Solve(prepare.Turnstile.DX, "") + turnstileToken = turnstile.SolveWithScripts(prepare.Turnstile.DX, "", cachedScripts) } } @@ -344,7 +345,7 @@ func POSTTurnStile(client httpclient.AuroraHttpClient, secret *tokens.Secret, pr client.SetProxy(proxy) } if cachedRequireProof == "" { - cachedRequireProof = prooftoken.NewPOWConfig(defaultUserAgent()).RequirementsToken() + cachedRequireProof = prooftoken.NewPOWConfig(defaultUserAgent(), cachedScripts, cachedDpl).RequirementsToken() } var apiUrl string if secret.IsFree { @@ -2240,6 +2241,9 @@ readLoop: } if streamEvent.finishReason != "" { finish_reason = streamEvent.finishReason + if finish_reason == "length" { + max_tokens = true + } isEnd = true } if activeChannel == "analysis" { @@ -2250,6 +2254,25 @@ readLoop: c.Writer.WriteString("data: " + finalLine.String() + "\n\n") c.Writer.Flush() } + if max_tokens && convId != "" && assistantMessageID != "" { + finalizeArtifacts() + return HandlerResult{ + Text: strings.Join(imgSource, "") + previous_text.Text, + ThinkingText: thinkingText, + ConversationID: convId, + ParentMessageID: assistantMessageID, + Sentinel: sentinel, + ArtifactSignals: artifactState.Signals, + SandboxArtifacts: artifactState.SandboxArtifacts, + PDFArtifacts: artifactState.PDFArtifacts, + GeneratedImageIDs: artifactState.ImageFileIDs, + StopSent: true, + Continue: &ContinueInfo{ + ConversationID: convId, + ParentID: assistantMessageID, + }, + } + } finalizeArtifacts() return HandlerResult{ Text: strings.Join(imgSource, "") + previous_text.Text, @@ -2294,6 +2317,25 @@ readLoop: previous_text.Text += deltaText } if streamEvent.isStop { + if max_tokens && convId != "" && assistantMessageID != "" { + finalizeArtifacts() + return HandlerResult{ + Text: strings.Join(imgSource, "") + previous_text.Text, + ThinkingText: thinkingText, + ConversationID: convId, + ParentMessageID: assistantMessageID, + Sentinel: sentinel, + ArtifactSignals: artifactState.Signals, + SandboxArtifacts: artifactState.SandboxArtifacts, + PDFArtifacts: artifactState.PDFArtifacts, + GeneratedImageIDs: artifactState.ImageFileIDs, + StopSent: true, + Continue: &ContinueInfo{ + ConversationID: convId, + ParentID: assistantMessageID, + }, + } + } finalizeArtifacts() return HandlerResult{ Text: strings.Join(imgSource, "") + previous_text.Text, diff --git a/internal/chatgpt/request_test.go b/internal/chatgpt/request_test.go index d4c0a588c..8a6ec2c4b 100644 --- a/internal/chatgpt/request_test.go +++ b/internal/chatgpt/request_test.go @@ -409,8 +409,11 @@ func TestCreateBaseHeaderMatchesWebClientShape(t *testing.T) { if first["oai-language"] != "zh-CN" { t.Fatalf("oai-language = %q, want zh-CN", first["oai-language"]) } - if !strings.Contains(first["user-agent"], "Edg/147.0.0.0") { - t.Fatalf("user-agent = %q, want Edge 147 shape", first["user-agent"]) + // UA must be the Edge variant to stay consistent with the hardcoded + // sec-ch-ua = "Microsoft Edge";v="146". Version is randomized. + ua := first["user-agent"] + if !strings.Contains(ua, "Edg/") { + t.Fatalf("user-agent = %q, want Edge variant to match sec-ch-ua=Microsoft Edge 146", ua) } if first["oai-device-id"] == "" || first["oai-device-id"] != second["oai-device-id"] { t.Fatalf("oai-device-id should be stable across headers: first=%q second=%q", first["oai-device-id"], second["oai-device-id"]) diff --git a/internal/prooftoken/prooftoken.go b/internal/prooftoken/prooftoken.go index b9075b3e8..a4ec744ee 100644 --- a/internal/prooftoken/prooftoken.go +++ b/internal/prooftoken/prooftoken.go @@ -8,10 +8,10 @@ import ( "fmt" "math/rand" "strconv" - "strings" - "sync/atomic" "time" + "aurora/internal/turnstile" + "golang.org/x/crypto/sha3" ) @@ -24,29 +24,67 @@ const ( maxRequirementsIter = 500_000 maxProofIter = 100_000 - powFallback = "gAAAAABwQ8Lk5FbGpA2NcR9dShT6gYjU7VxZ4D" + powFallback = "wQ8Lk5FbGpA2NcR9dShT6gYjU7VxZ4D" ) var ( - powCores = []int{16, 24, 32} - powScreens = []int{3000, 4000, 6000} + powCores = []int{8, 16, 24, 32} + powScreens = []int{3000, 4000, 5000} powNavKeys = []string{ - "webdriver-false", "vendor-Google Inc.", "cookieEnabled-true", - "pdfViewerEnabled-true", "hardwareConcurrency-32", - "language-zh-CN", "mimeTypes-[object MimeTypeArray]", - "userAgentData-[object NavigatorUAData]", + "registerProtocolHandler−function registerProtocolHandler() { [native code] }", + "storage−[object StorageManager]", + "locks−[object LockManager]", + "appCodeName−Mozilla", + "permissions−[object Permissions]", + "share−function share() { [native code] }", + "webdriver−false", + "managed−[object NavigatorManagedData]", + "canShare−function canShare() { [native code] }", + "vendor−Google Inc.", + "mediaDevices−[object MediaDevices]", + "vibrate−function vibrate() { [native code] }", + "storageBuckets−[object StorageBucketManager]", + "mediaCapabilities−[object MediaCapabilities]", + "cookieEnabled−true", + "virtualKeyboard−[object VirtualKeyboard]", + "product−Gecko", + "presentation−[object Presentation]", + "onLine−true", + "mimeTypes−[object MimeTypeArray]", + "credentials−[object CredentialsContainer]", + "serviceWorker−[object ServiceWorkerContainer]", + "keyboard−[object Keyboard]", + "gpu−[object GPU]", + "doNotTrack", + "serial−[object Serial]", + "pdfViewerEnabled−true", + "language−zh-CN", + "geolocation−[object Geolocation]", + "userAgentData−[object NavigatorUAData]", + "getUserMedia−function getUserMedia() { [native code] }", + "sendBeacon−function sendBeacon() { [native code] }", + "hardwareConcurrency−32", + "windowControlsOverlay−[object WindowControlsOverlay]", } powWinKeys = []string{ - "innerWidth", "innerHeight", "devicePixelRatio", "screen", - "chrome", "location", "history", "navigator", + "0", "window", "self", "document", "name", "location", + "customElements", "history", "navigation", + "innerWidth", "innerHeight", "scrollX", "scrollY", + "visualViewport", "screenX", "screenY", + "outerWidth", "outerHeight", "devicePixelRatio", + "screen", "chrome", "navigator", + "onresize", "performance", "crypto", + "indexedDB", "sessionStorage", "localStorage", "scheduler", + "alert", "atob", "btoa", "fetch", "matchMedia", + "postMessage", "queueMicrotask", "requestAnimationFrame", + "setInterval", "setTimeout", "caches", + "__NEXT_DATA__", "__BUILD_MANIFEST", "__NEXT_PRELOADREADY", } - powReactListeners = []string{"_reactListeningcfilawjnerp", "_reactListening9ne2dfo1i47"} - powProofEvents = []string{"alert", "ontransitionend", "onprogress"} + powDocKeys = []string{"_reactListeningo743lnnpvdg", "location"} - // perfCounter 模拟浏览器 performance.counter() 的单调递增(亚秒级)。 - perfCounter uint64 + defaultScriptSources = []string{"https://chatgpt.com/backend-api/sentinel/sdk.js"} ) // POWConfig 是 18 元素的客户端指纹数组(requirements_token 用)。 @@ -55,43 +93,48 @@ type POWConfig struct { arr [18]interface{} } -// NewPOWConfig 构造一个随机化的客户端指纹,用于 requirements + proof 两种场景。 -func NewPOWConfig(userAgent string) *POWConfig { +func NewPOWConfig(userAgent string, scriptSources []string, dataBuild string) *POWConfig { if userAgent == "" { userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/147.0.0.0 Safari/537.36 Edg/147.0.0.0" } + if len(scriptSources) == 0 { + scriptSources = defaultScriptSources + } //nolint:gosec // 非加密用途 rng := rand.New(rand.NewSource(time.Now().UnixNano())) - now := time.Now().UTC() - timeStr := now.Format("Mon Jan 02 2006 15:04:05") + " GMT+0000 (UTC)" - perf := float64(atomic.AddUint64(&perfCounter, 1)) + rng.Float64() + perfNow := float64(time.Now().UnixNano()) / 1e6 // performance.now() 等价 + timeStr := _legacyParseTime() + scriptSrc := scriptSources[rng.Intn(len(scriptSources))] c := &POWConfig{userAgent: userAgent} c.arr = [18]interface{}{ - powCores[rng.Intn(len(powCores))] + powScreens[rng.Intn(len(powScreens))], // 0 - timeStr, // 1 - nil, // 2 - rng.Float64(), // 3 - 迭代会覆盖 - userAgent, // 4 - nil, // 5 - "dpl=1440a687921de39ff5ee56b92807faaadce73f13", // 6 + powScreens[rng.Intn(len(powScreens))], // 0 — screen value + timeStr, // 1 + 4294705152, // 2 — 硬编码常量 + 0, // 3 — 迭代会覆盖 + userAgent, // 4 + scriptSrc, // 5 —