package main import ( "context" "encoding/json" "fmt" "log" "net/http" "os" "path/filepath" "runtime" "strconv" "strings" "sync" "time" "github.com/chromedp/cdproto/emulation" "github.com/chromedp/cdproto/network" "github.com/chromedp/cdproto/security" "github.com/chromedp/cdproto/target" "github.com/chromedp/chromedp" ) var ( globalAllocatorCancel context.CancelFunc globalBrowserCtx context.Context statsMu sync.Mutex totalRequests int64 totalErrors int64 activeTabs int64 startTime time.Time tmpDir string ) type AdvancedAction struct { Type string `json:"type"` Selector string `json:"selector,omitempty"` Value string `json:"value,omitempty"` FileName string `json:"fileName,omitempty"` } type Payload struct { Timeout int `json:"timeout,omitempty"` Width int `json:"width,omitempty"` Height int `json:"height,omitempty"` UserAgent string `json:"userAgent,omitempty"` IgnoreHTTPSErrors bool `json:"ignoreHTTPSErrors,omitempty"` DisableJS bool `json:"disableJS,omitempty"` BlockImages bool `json:"blockImages,omitempty"` SniffNetwork bool `json:"sniffNetwork,omitempty"` MaxSniffSize int `json:"maxSniffSize,omitempty"` Headers map[string]interface{} `json:"headers,omitempty"` Actions []AdvancedAction `json:"actions"` } type StandardResponse struct { Logs []string `json:"logs"` Result map[string]interface{} `json:"result"` Error string `json:"error,omitempty"` } type StatsResponse struct { Uptime string `json:"uptime"` TotalRequests int64 `json:"totalRequests"` TotalErrors int64 `json:"totalErrors"` ActiveTabs int64 `json:"activeTabs"` OpenPagesCount int `json:"openPagesCount"` GoVersion string `json:"goVersion"` NumGoroutine int `json:"numGoroutine"` AllocMemoryMB uint64 `json:"allocMemoryMB"` TotalAllocMB uint64 `json:"totalAllocMB"` SysMemoryMB uint64 `json:"sysMemoryMB"` NumGC uint32 `json:"numGC"` } type SniffedNetworkActivity struct { URL string `json:"url"` Method string `json:"method"` RequestHeaders map[string]string `json:"requestHeaders,omitempty"` ResponseStatus int `json:"responseStatus,omitempty"` ResponseHeaders map[string]string `json:"responseHeaders,omitempty"` ResponseBody string `json:"responseBody,omitempty"` } func main() { port := "7860" startTime = time.Now() tmpDir = os.TempDir() initGlobalBrowser() defer globalAllocatorCancel() http.HandleFunc("/", statsHandler) http.HandleFunc("/execute", executeHandler) // http.HandleFunc("/stats", statsHandler) fs := http.FileServer(http.Dir(tmpDir)) http.Handle("/tmp/", http.StripPrefix("/tmp/", fs)) fmt.Printf("Running on port %s...\n", port) if err := http.ListenAndServe(":"+port, nil); err != nil { log.Fatal(err) } } func initGlobalBrowser() { opts := append(chromedp.DefaultExecAllocatorOptions[:], chromedp.NoSandbox, chromedp.Flag("disable-sandbox", true), chromedp.Headless, chromedp.DisableGPU, chromedp.Flag("disable-dev-shm-usage", true), chromedp.Flag("disable-blink-features", "AutomationControlled"), chromedp.Flag("exclude-switches", "enable-automation"), chromedp.Flag("use-fake-codec-for-media-stream", true), chromedp.Flag("disable-gl-drawing-for-tests", true), chromedp.Flag("disable-software-rasterizer", true), chromedp.Flag("disable-canvas-aa", true), chromedp.Flag("disable-2d-canvas-clip-antialiasing", true), chromedp.Flag("disable-2d-canvas-image-chromium", true), chromedp.Flag("disable-gpu-rasterization", true), chromedp.Flag("no-zygote", true), chromedp.Flag("single-process", true), chromedp.Flag("disable-infobars", true), chromedp.Flag("disable-notifications", true), chromedp.Flag("disable-popup-blocking", true), chromedp.Flag("disable-background-networking", true), chromedp.Flag("disable-renderer-backgrounding", true), chromedp.Flag("no-first-run", true), chromedp.Flag("no-default-browser-check", true), chromedp.Flag("window-size", "1366,768"), chromedp.Flag("lang", "en-US,en"), chromedp.Flag("platform", "Win32"), chromedp.Flag("blink-settings", "primaryHoverType=2,availableHoverTypes=2,primaryPointerType=4,availablePointerTypes=4"), ) allocCtx, cancelAlloc := chromedp.NewExecAllocator(context.Background(), opts...) globalAllocatorCancel = cancelAlloc ctx, _ := chromedp.NewContext(allocCtx) globalBrowserCtx = ctx if err := chromedp.Run(globalBrowserCtx); err != nil { log.Fatalf("Failed to initialize Chromium: %v", err) } } func executeHandler(w http.ResponseWriter, r *http.Request) { statsMu.Lock() totalRequests++ activeTabs++ statsMu.Unlock() defer func() { statsMu.Lock() activeTabs-- statsMu.Unlock() }() if r.Method != http.MethodPost { http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed) return } var req Payload if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } tabTimeout := 30 if req.Timeout > 0 { tabTimeout = req.Timeout } tabCtx, cancelTab := chromedp.NewContext(globalBrowserCtx) defer cancelTab() timeoutCtx, cancelTimeout := context.WithTimeout(tabCtx, time.Duration(tabTimeout)*time.Second) defer cancelTimeout() var logs []string results := make(map[string]interface{}) var screenshots []string var evals []interface{} var networkMu sync.Mutex networkRegistry := make(map[network.RequestID]*SniffedNetworkActivity) var closedRequestIDs []network.RequestID if req.SniffNetwork { _ = chromedp.Run(timeoutCtx, network.Enable()) chromedp.ListenTarget(timeoutCtx, func(ev interface{}) { switch e := ev.(type) { case *network.EventRequestWillBeSent: networkMu.Lock() reqHeaders := make(map[string]string) for k, v := range e.Request.Headers { if strVal, ok := v.(string); ok { reqHeaders[k] = strVal } } networkRegistry[e.RequestID] = &SniffedNetworkActivity{ URL: e.Request.URL, Method: e.Request.Method, RequestHeaders: reqHeaders, } networkMu.Unlock() case *network.EventResponseReceived: networkMu.Lock() if activity, exists := networkRegistry[e.RequestID]; exists { activity.ResponseStatus = int(e.Response.Status) resHeaders := make(map[string]string) for k, v := range e.Response.Headers { if strVal, ok := v.(string); ok { resHeaders[k] = strVal } } activity.ResponseHeaders = resHeaders } networkMu.Unlock() case *network.EventLoadingFinished: networkMu.Lock() closedRequestIDs = append(closedRequestIDs, e.RequestID) networkMu.Unlock() } }) } _ = chromedp.Run(timeoutCtx, chromedp.ActionFunc(func(ctx context.Context) error { _ = target.SetAutoAttach(true, false).WithFlatten(true).Do(ctx) hookScript := ` window.__turnstileToken = ''; const hookTurnstile = () => { if (!window.turnstile || window.turnstile.__hooked) return; const origRender = window.turnstile.render.bind(window.turnstile); window.turnstile.render = function(container, params) { if (params && typeof params.callback === 'function') { const origCb = params.callback; params.callback = function(token) { window.__turnstileToken = token; return origCb(token); }; } return origRender(container, params); }; window.turnstile.__hooked = true; }; Object.defineProperty(window, 'turnstile', { configurable: true, set(v) { this.__ts = v; setTimeout(hookTurnstile, 0); }, get() { return this.__ts; } }); ` var tmp interface{} return chromedp.Evaluate(hookScript, &tmp).Do(ctx) })) if req.Width > 0 && req.Height > 0 { logs = append(logs, fmt.Sprintf("Setup Viewport: %dx%d", req.Width, req.Height)) _ = chromedp.Run(timeoutCtx, chromedp.EmulateViewport(int64(req.Width), int64(req.Height))) } else { _ = chromedp.Run(timeoutCtx, chromedp.EmulateViewport(1366, 768)) } defaultUA := "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36" if req.UserAgent != "" { defaultUA = req.UserAgent } _ = chromedp.Run(timeoutCtx, chromedp.ActionFunc(func(ctx context.Context) error { return emulation.SetUserAgentOverride(defaultUA).Do(ctx) })) baseHeaders := network.Headers{ "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8", "Accept-Language": "en-US,en;q=0.9", "Cache-Control": "no-cache", "Pragma": "no-cache", "Sec-Ch-Ua": `"Chromium";v="124", "Not A(Brand";v="24", "Google Chrome";v="124"`, "Sec-Ch-Ua-Mobile": "?0", "Sec-Ch-Ua-Platform": `"Windows"`, "Sec-Fetch-Dest": "document", "Sec-Fetch-Mode": "navigate", "Sec-Fetch-Site": "none", "Sec-Fetch-User": "?1", "Upgrade-Insecure-Requests": "1", } for k, v := range req.Headers { baseHeaders[k] = v } _ = chromedp.Run(timeoutCtx, network.SetExtraHTTPHeaders(baseHeaders)) if req.IgnoreHTTPSErrors { logs = append(logs, "Setup Ignore HTTPS Errors") _ = chromedp.Run(timeoutCtx, security.SetIgnoreCertificateErrors(true)) } if req.DisableJS { logs = append(logs, "Setup Disable JavaScript") _ = chromedp.Run(timeoutCtx, emulation.SetScriptExecutionDisabled(true)) } for i, act := range req.Actions { logMsg := fmt.Sprintf("Step %d: [%s]", i+1, act.Type) if act.Selector != "" { logMsg += fmt.Sprintf(" -> Target: %s", act.Selector) } logs = append(logs, logMsg) var err error switch act.Type { case "navigate": err = chromedp.Run(timeoutCtx, chromedp.Navigate(act.Value)) if err == nil && req.BlockImages { logs = append(logs, "Injecting CSS to block images visual rendering") var tmp interface{} injectStyleScript := `const style = document.createElement('style'); style.innerText = 'img, svg, picture, [style*="background-image"] { display: none !important; }'; document.head.appendChild(style);` _ = chromedp.Run(timeoutCtx, chromedp.Evaluate(injectStyleScript, &tmp)) } case "wait": err = chromedp.Run(timeoutCtx, chromedp.WaitReady(act.Selector, chromedp.ByQuery)) case "click": err = chromedp.Run(timeoutCtx, chromedp.Click(act.Selector, chromedp.ByQuery)) case "type": err = chromedp.Run(timeoutCtx, chromedp.SendKeys(act.Selector, act.Value, chromedp.ByQuery)) case "clear": err = chromedp.Run(timeoutCtx, chromedp.Clear(act.Selector, chromedp.ByQuery)) case "hover": var tmp interface{} hoverScript := fmt.Sprintf(`const e = document.querySelector("%s"); if(e) { e.dispatchEvent(new MouseEvent('mouseover', {bubbles: true})); }`, act.Selector) err = chromedp.Run(timeoutCtx, chromedp.Evaluate(hoverScript, &tmp)) case "select": var tmp interface{} selectScript := fmt.Sprintf(`const e = document.querySelector("%s"); if(e) { e.value = "%s"; e.dispatchEvent(new Event('change', {bubbles: true})); }`, act.Selector, act.Value) err = chromedp.Run(timeoutCtx, chromedp.Evaluate(selectScript, &tmp)) case "key": var tmp interface{} keyScript := fmt.Sprintf(`const e = document.activeElement; if(e) { e.dispatchEvent(new KeyboardEvent('keydown', {'key': '%s', bubbles: true})); }`, act.Value) err = chromedp.Run(timeoutCtx, chromedp.Evaluate(keyScript, &tmp)) case "scroll": if act.Selector != "" { err = chromedp.Run(timeoutCtx, chromedp.ScrollIntoView(act.Selector, chromedp.ByQuery)) } else { scrollScript := fmt.Sprintf("window.scrollTo(0, %s);", act.Value) var tmp interface{} err = chromedp.Run(timeoutCtx, chromedp.Evaluate(scrollScript, &tmp)) } case "setLocation": coords := strings.Split(act.Value, ",") lat, _ := strconv.ParseFloat(coords[0], 64) long, _ := strconv.ParseFloat(coords[1], 64) timezone := coords[2] err = chromedp.Run(timeoutCtx, emulation.SetGeolocationOverride().WithLatitude(lat).WithLongitude(long).WithAccuracy(100), emulation.SetTimezoneOverride(timezone), ) case "evaluate": var jsResult interface{} err = chromedp.Run(timeoutCtx, chromedp.Evaluate(act.Value, &jsResult)) if err == nil { evals = append(evals, jsResult) } case "extractTurnstile": logs = append(logs, "Attempting to bypass and extract Cloudflare Turnstile...") var coordinates map[string]interface{} findIframeScript := ` (() => { const iframe = document.querySelector('iframe[src*="challenges.cloudflare.com"]') || document.querySelector('iframe[title*="verification"]') || document.querySelector('.cf-turnstile'); if (!iframe) return null; const rect = iframe.getBoundingClientRect(); return { x: rect.left + (rect.width / 2), y: rect.top + (rect.height / 2), found: true }; })(); ` for retry := 0; retry < 15; retry++ { _ = chromedp.Run(timeoutCtx, chromedp.Evaluate(findIframeScript, &coordinates)) if coordinates != nil && coordinates["found"] == true { break } time.Sleep(500 * time.Millisecond) } if coordinates != nil && coordinates["found"] == true { posX := coordinates["x"].(float64) posY := coordinates["y"].(float64) logs = append(logs, fmt.Sprintf("Interactive Turnstile detected. Sending click to coordinate: X=%.2f, Y=%.2f", posX, posY)) err = chromedp.Run(timeoutCtx, chromedp.MouseClickXY(posX, posY)) } var token interface{} harvestScript := ` (async () => { for (let i = 0; i < 40; i++) { if (window.__turnstileToken && window.__turnstileToken.length > 10) { return window.__turnstileToken; } let input = document.querySelector('input[name="cf-turnstile-response"]') || document.querySelector('input[name="g-recaptcha-response"]') || document.getElementById('cf-turnstile-response'); if (input && input.value && input.value.length > 10) { return input.value; } await new Promise(r => setTimeout(r, 700)); } return ""; })(); ` err = chromedp.Run(timeoutCtx, chromedp.Evaluate(harvestScript, &token)) if err == nil && token != "" { logs = append(logs, "Cloudflare Turnstile bypassed successfully via Object Hook/DOM!") results["turnstileToken"] = token } else { networkMu.Lock() foundToken := "" for _, activity := range networkRegistry { if strings.Contains(activity.URL, "/v0/g/") && activity.Method == "POST" { lines := strings.Split(activity.URL, "/") if len(lines) > 0 { foundToken = lines[len(lines)-1] } } } networkMu.Unlock() if foundToken != "" { logs = append(logs, "Cloudflare Turnstile extracted via active Network Sniffing trace!") results["turnstileToken"] = foundToken } else { logs = append(logs, "Turnstile verification failed or timed out.") results["turnstileToken"] = "FAILED_OR_TIMEOUT" } } case "screenshot": var buf []byte if act.Selector != "" { err = chromedp.Run(timeoutCtx, chromedp.Screenshot(act.Selector, &buf, chromedp.ByQuery)) } else { err = chromedp.Run(timeoutCtx, chromedp.CaptureScreenshot(&buf)) } if err == nil { name := act.FileName if name == "" { name = fmt.Sprintf("ss-%d.png", time.Now().UnixNano()) } name = strings.ReplaceAll(name, " ", "_") filePath := filepath.Join(tmpDir, name) err = os.WriteFile(filePath, buf, 0644) if err == nil { screenshots = append(screenshots, "/tmp/"+name) } } case "screenshotFullPage": var buf []byte var width, height int64 sizeScript := `(() => { return { width: Math.max(document.body.scrollWidth, document.documentElement.scrollWidth), height: Math.max(document.body.scrollHeight, document.documentElement.scrollHeight) }; })()` var dimensions map[string]interface{} err = chromedp.Run(timeoutCtx, chromedp.Evaluate(sizeScript, &dimensions)) if err == nil && dimensions != nil { width = int64(dimensions["width"].(float64)) height = int64(dimensions["height"].(float64)) _ = chromedp.Run(timeoutCtx, chromedp.EmulateViewport(width, height)) err = chromedp.Run(timeoutCtx, chromedp.CaptureScreenshot(&buf)) } if err == nil { name := act.FileName if name == "" { name = fmt.Sprintf("fullss-%d.png", time.Now().UnixNano()) } name = strings.ReplaceAll(name, " ", "_") filePath := filepath.Join(tmpDir, name) err = os.WriteFile(filePath, buf, 0644) if err == nil { screenshots = append(screenshots, "/tmp/"+name) } } } if err != nil { statsMu.Lock() totalErrors++ statsMu.Unlock() returnError(w, logs, fmt.Errorf("Failed in step %d (%s): %w", i+1, act.Type, err)) return } } results["screenshots"] = screenshots results["evals"] = evals if req.SniffNetwork { maxSize := 1024 * 50 if req.MaxSniffSize > 0 { maxSize = req.MaxSniffSize } networkMu.Lock() copiedIDs := make([]network.RequestID, len(closedRequestIDs)) copy(copiedIDs, closedRequestIDs) copiedRegistry := make(map[network.RequestID]SniffedNetworkActivity) for _, id := range copiedIDs { if actPtr, exists := networkRegistry[id]; exists && actPtr != nil { copiedRegistry[id] = *actPtr } } networkMu.Unlock() var finalNetworkLogs []SniffedNetworkActivity for _, reqID := range copiedIDs { activity, exists := copiedRegistry[reqID] if !exists { continue } contentType := strings.ToLower(activity.ResponseHeaders["content-type"]) isData := strings.Contains(contentType, "json") || strings.Contains(contentType, "text/html") || strings.Contains(contentType, "javascript") if isData && activity.ResponseStatus == 200 { var bodyBuf []byte sniffCtx, sniffCancel := context.WithTimeout(timeoutCtx, 1*time.Second) _ = chromedp.Run(sniffCtx, chromedp.ActionFunc(func(ctx context.Context) error { var err error bodyBuf, err = network.GetResponseBody(reqID).Do(ctx) return err })) sniffCancel() if len(bodyBuf) > 0 { if len(bodyBuf) > maxSize { activity.ResponseBody = string(bodyBuf[:maxSize]) + " ... [TRUNCATED]" } else { activity.ResponseBody = string(bodyBuf) } } else { activity.ResponseBody = "[SKIPPED: BODY ACQUISITION TIMEOUT OR EMPTY]" } } else { activity.ResponseBody = "[SKIPPED: NON-DATA OR LARGE ASSET]" } finalNetworkLogs = append(finalNetworkLogs, activity) } results["networkLogs"] = finalNetworkLogs } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(StandardResponse{ Logs: logs, Result: results, }) } func statsHandler(w http.ResponseWriter, r *http.Request) { var m runtime.MemStats runtime.ReadMemStats(&m) var targets []*target.Info ctx, cancel := context.WithTimeout(globalBrowserCtx, 2*time.Second) infos, err := chromedp.Targets(ctx) cancel() if err == nil { targets = infos } pagesCount := 0 for _, t := range targets { if t.Type == "page" { pagesCount++ } } statsMu.Lock() res := StatsResponse{ Uptime: time.Since(startTime).String(), TotalRequests: totalRequests, TotalErrors: totalErrors, ActiveTabs: activeTabs, OpenPagesCount: pagesCount, GoVersion: runtime.Version(), NumGoroutine: runtime.NumGoroutine(), AllocMemoryMB: m.Alloc / 1024 / 1024, TotalAllocMB: m.TotalAlloc / 1024 / 1024, SysMemoryMB: m.Sys / 1024 / 1024, NumGC: m.NumGC, } statsMu.Unlock() w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(res) } func returnError(w http.ResponseWriter, logs []string, err error) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusInternalServerError) json.NewEncoder(w).Encode(StandardResponse{ Logs: logs, Error: err.Error(), }) }