package main import ( "context" "fmt" "log" "os" "strings" "time" "github.com/chromedp/chromedp" ) func main() { if len(os.Args) < 2 { fmt.Println("Usage: go run jackbox-player-count.go ") os.Exit(1) } roomCode := strings.ToUpper(os.Args[1]) count, err := getPlayerCount(roomCode) if err != nil { fmt.Printf("Error: %v\n", err) os.Exit(1) } fmt.Printf("%d\n", count) } func getPlayerCount(roomCode string) (int, error) { // Suppress cookie errors opts := append(chromedp.DefaultExecAllocatorOptions[:], chromedp.Flag("headless", true), ) allocCtx, cancel := chromedp.NewExecAllocator(context.Background(), opts...) defer cancel() ctx, cancel := chromedp.NewContext(allocCtx, chromedp.WithLogf(func(s string, i ...interface{}) { msg := fmt.Sprintf(s, i...) if !strings.Contains(msg, "cookie") { log.Printf(msg) } })) defer cancel() ctx, cancel = context.WithTimeout(ctx, 30*time.Second) defer cancel() var playerCount int err := chromedp.Run(ctx, chromedp.Navigate("https://jackbox.tv/"), chromedp.WaitVisible(`input[placeholder*="ENTER 4-LETTER CODE"]`), // Inject WebSocket hook BEFORE joining chromedp.ActionFunc(func(ctx context.Context) error { return chromedp.Evaluate(` window.__jackboxPlayerCount = null; const OriginalWebSocket = window.WebSocket; window.WebSocket = function(...args) { const ws = new OriginalWebSocket(...args); const originalAddEventListener = ws.addEventListener; ws.addEventListener = function(type, listener, ...rest) { if (type === 'message') { const wrappedListener = function(event) { try { const data = JSON.parse(event.data); if (data.opcode === 'client/welcome' && data.result && data.result.here) { window.__jackboxPlayerCount = Object.keys(data.result.here).length; } } catch (e) {} return listener.call(this, event); }; return originalAddEventListener.call(this, type, wrappedListener, ...rest); } return originalAddEventListener.call(this, type, listener, ...rest); }; return ws; }; `, nil).Do(ctx) }), // Now join chromedp.SendKeys(`input[placeholder*="ENTER 4-LETTER CODE"]`, roomCode+"\n\n"), // Poll for the value chromedp.ActionFunc(func(ctx context.Context) error { for i := 0; i < 60; i++ { // Try for 30 seconds time.Sleep(500 * time.Millisecond) var count int err := chromedp.Evaluate(`window.__jackboxPlayerCount || -1`, &count).Do(ctx) if err == nil && count > 0 { playerCount = count return nil } } return fmt.Errorf("timeout waiting for player count") }), ) if err != nil { return 0, err } if playerCount < 0 { return 0, fmt.Errorf("could not find player count") } return playerCount, nil }