Files

325 lines
10 KiB
Go
Raw Permalink Normal View History

package main
import (
2025-11-01 10:40:53 -04:00
"encoding/json"
"fmt"
"log"
"os"
"os/signal"
2025-11-01 10:40:53 -04:00
"path/filepath"
"time"
"github.com/playwright-community/playwright-go"
)
const (
roomURL = "https://app.kosmi.io/room/@hyperspaceout"
2025-11-01 10:40:53 -04:00
logFile = "image-upload-capture.log"
)
func main() {
2025-11-01 10:40:53 -04:00
log.Println("🔍 Starting Kosmi WebSocket Monitor (Image Upload Capture)")
log.Printf("📡 Room URL: %s", roomURL)
2025-11-01 10:40:53 -04:00
log.Printf("📝 Log file: %s", logFile)
log.Println("This will capture ALL WebSocket traffic and HTTP requests...")
log.Println()
2025-11-01 10:40:53 -04:00
// Create log file
f, err := os.Create(logFile)
if err != nil {
log.Fatalf("Failed to create log file: %v", err)
}
defer f.Close()
// Set up interrupt handler
interrupt := make(chan os.Signal, 1)
signal.Notify(interrupt, os.Interrupt)
2025-11-01 10:40:53 -04:00
// Helper to log to both console and file
logBoth := func(format string, args ...interface{}) {
msg := fmt.Sprintf(format, args...)
log.Println(msg)
fmt.Fprintf(f, "%s %s\n", time.Now().Format("15:04:05.000"), msg)
}
// Launch Playwright
pw, err := playwright.Run()
if err != nil {
log.Fatalf("Failed to start Playwright: %v", err)
}
defer pw.Stop()
// Launch browser
browser, err := pw.Chromium.Launch(playwright.BrowserTypeLaunchOptions{
Headless: playwright.Bool(false), // Keep visible so we can see what's happening
})
if err != nil {
log.Fatalf("Failed to launch browser: %v", err)
}
defer browser.Close()
2025-11-01 10:40:53 -04:00
// Create context with permissions
context, err := browser.NewContext(playwright.BrowserNewContextOptions{
UserAgent: playwright.String("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"),
2025-11-01 10:40:53 -04:00
AcceptDownloads: playwright.Bool(true),
// Grant all permissions to avoid upload blocking
Permissions: []string{"clipboard-read", "clipboard-write"},
})
if err != nil {
log.Fatalf("Failed to create context: %v", err)
}
2025-11-01 10:40:53 -04:00
// Grant permissions for the Kosmi domain
if err := context.GrantPermissions([]string{"clipboard-read", "clipboard-write"}, playwright.BrowserContextGrantPermissionsOptions{
Origin: playwright.String("https://app.kosmi.io"),
}); err != nil {
logBoth("⚠️ Warning: Failed to grant permissions: %v", err)
}
// Listen to all network requests
context.On("request", func(request playwright.Request) {
logBoth("🌐 [HTTP REQUEST] %s %s", request.Method(), request.URL())
postData, err := request.PostData()
if err == nil && postData != "" {
logBoth(" POST Data: %s", postData)
}
})
context.On("response", func(response playwright.Response) {
status := response.Status()
url := response.URL()
logBoth("📨 [HTTP RESPONSE] %d %s", status, url)
// Try to get response body for file uploads
if status >= 200 && status < 300 {
body, err := response.Body()
if err == nil && len(body) > 0 && len(body) < 10000 {
// Log if it looks like JSON
var jsonData interface{}
if json.Unmarshal(body, &jsonData) == nil {
prettyJSON, _ := json.MarshalIndent(jsonData, " ", " ")
logBoth(" Response Body: %s", string(prettyJSON))
}
}
}
})
// Create page
page, err := context.NewPage()
if err != nil {
log.Fatalf("Failed to create page: %v", err)
}
// Inject WebSocket monitoring script BEFORE navigation
2025-11-01 10:40:53 -04:00
logBoth("📝 Injecting WebSocket and file upload monitoring script...")
if err := page.AddInitScript(playwright.Script{
Content: playwright.String(`
(function() {
const OriginalWebSocket = window.WebSocket;
let messageCount = 0;
window.WebSocket = function(url, protocols) {
console.log('🔌 [WS MONITOR] WebSocket created:', url, 'protocols:', protocols);
const socket = new OriginalWebSocket(url, protocols);
socket.addEventListener('open', (event) => {
console.log(' [WS MONITOR] WebSocket OPENED');
});
socket.addEventListener('close', (event) => {
console.log('🔴 [WS MONITOR] WebSocket CLOSED:', event.code, event.reason);
});
socket.addEventListener('error', (event) => {
console.error(' [WS MONITOR] WebSocket ERROR:', event);
});
// Intercept outgoing messages
const originalSend = socket.send;
socket.send = function(data) {
messageCount++;
2025-11-01 10:40:53 -04:00
// Check if binary data
if (data instanceof ArrayBuffer || data instanceof Blob) {
console.log('📤 [WS MONITOR] SEND #' + messageCount + ': [BINARY DATA]',
'Type:', data.constructor.name,
'Size:', data.byteLength || data.size);
} else {
console.log('📤 [WS MONITOR] SEND #' + messageCount + ':', data);
try {
const parsed = JSON.parse(data);
console.log(' Type:', parsed.type, 'ID:', parsed.id);
if (parsed.payload) {
console.log(' Payload:', JSON.stringify(parsed.payload, null, 2));
}
} catch (e) {
// Not JSON
}
}
return originalSend.call(this, data);
};
// Intercept incoming messages
socket.addEventListener('message', (event) => {
messageCount++;
2025-11-01 10:40:53 -04:00
// Check if binary data
if (event.data instanceof ArrayBuffer || event.data instanceof Blob) {
console.log('📥 [WS MONITOR] RECEIVE #' + messageCount + ': [BINARY DATA]',
'Type:', event.data.constructor.name,
'Size:', event.data.byteLength || event.data.size);
} else {
console.log('📥 [WS MONITOR] RECEIVE #' + messageCount + ':', event.data);
try {
const parsed = JSON.parse(event.data);
console.log(' Type:', parsed.type, 'ID:', parsed.id);
if (parsed.payload) {
console.log(' Payload:', JSON.stringify(parsed.payload, null, 2));
}
} catch (e) {
// Not JSON
}
}
});
return socket;
};
// Preserve WebSocket properties
window.WebSocket.prototype = OriginalWebSocket.prototype;
window.WebSocket.CONNECTING = OriginalWebSocket.CONNECTING;
window.WebSocket.OPEN = OriginalWebSocket.OPEN;
window.WebSocket.CLOSING = OriginalWebSocket.CLOSING;
window.WebSocket.CLOSED = OriginalWebSocket.CLOSED;
})();
`),
}); err != nil {
log.Fatalf("Failed to inject script: %v", err)
}
// Listen to console messages
page.On("console", func(msg playwright.ConsoleMessage) {
text := msg.Text()
msgType := msg.Type()
// Format the output nicely
prefix := "💬"
switch msgType {
case "log":
prefix = "📋"
case "error":
prefix = "❌"
case "warning":
prefix = "⚠️"
case "info":
prefix = ""
}
2025-11-01 10:40:53 -04:00
logBoth("%s [BROWSER %s] %s", prefix, msgType, text)
})
// Listen to file chooser events
page.On("filechooser", func(fileChooser playwright.FileChooser) {
logBoth("📁 [FILE CHOOSER] File dialog opened!")
logBoth(" Multiple: %v", fileChooser.IsMultiple())
// Try to set the test image
absPath, err := filepath.Abs("blurt.jpg")
if err != nil {
logBoth(" ❌ Failed to get absolute path: %v", err)
return
}
logBoth(" 📎 Setting file: %s", absPath)
if err := fileChooser.SetFiles(absPath, playwright.FileChooserSetFilesOptions{}); err != nil {
logBoth(" ❌ Failed to set file: %v", err)
} else {
logBoth(" ✅ File set successfully!")
}
})
// Navigate to room
2025-11-01 10:40:53 -04:00
logBoth("🌐 Navigating to %s...", roomURL)
if _, err := page.Goto(roomURL, playwright.PageGotoOptions{
WaitUntil: playwright.WaitUntilStateDomcontentloaded,
}); err != nil {
log.Fatalf("Failed to navigate: %v", err)
}
2025-11-01 10:40:53 -04:00
logBoth("✅ Page loaded! Monitoring WebSocket traffic and HTTP requests...")
logBoth("📸 Please manually upload the test image (blurt.jpg) in the Kosmi chat")
logBoth(" Look for the attachment/upload button in the chat interface")
logBoth("Press Ctrl+C when done to stop monitoring")
logBoth("")
// Wait for a bit to see initial traffic
time.Sleep(5 * time.Second)
2025-11-01 10:40:53 -04:00
// Try to trigger file upload programmatically (fallback to manual)
logBoth("\n📎 Attempting to trigger file upload dialog...")
result, err := page.Evaluate(`
(async function() {
2025-11-01 10:40:53 -04:00
// Try to find file input or upload button
const fileInputs = document.querySelectorAll('input[type="file"]');
if (fileInputs.length > 0) {
console.log('Found', fileInputs.length, 'file inputs');
fileInputs[0].click();
return { success: true, method: 'file-input' };
}
// Try to find buttons with upload-related text/icons
const buttons = document.querySelectorAll('button, [role="button"]');
for (let btn of buttons) {
const text = btn.textContent.toLowerCase();
const ariaLabel = btn.getAttribute('aria-label')?.toLowerCase() || '';
if (text.includes('upload') || text.includes('attach') || text.includes('file') ||
ariaLabel.includes('upload') || ariaLabel.includes('attach') || ariaLabel.includes('file')) {
console.log('Found upload button:', btn.textContent, ariaLabel);
btn.click();
return { success: true, method: 'upload-button' };
}
}
2025-11-01 10:40:53 -04:00
return { success: false, error: 'No upload button found' };
})();
`)
if err != nil {
2025-11-01 10:40:53 -04:00
logBoth("❌ Failed to trigger upload: %v", err)
logBoth("⚠️ FALLBACK: Please manually click the upload/attachment button in Kosmi")
} else {
resultMap := result.(map[string]interface{})
if success, ok := resultMap["success"].(bool); ok && success {
method := resultMap["method"].(string)
2025-11-01 10:40:53 -04:00
logBoth("✅ Triggered upload via %s", method)
} else {
2025-11-01 10:40:53 -04:00
logBoth("❌ Could not find upload button: %v", resultMap["error"])
logBoth("⚠️ FALLBACK: Please manually click the upload/attachment button in Kosmi")
}
}
// Keep monitoring
2025-11-01 10:40:53 -04:00
logBoth("\n⏳ Monitoring all traffic... Upload an image in Kosmi, then press Ctrl+C to stop")
logBoth("💡 TIP: If upload doesn't work in this browser, just note what you see in a normal browser")
logBoth(" We mainly need to see the HTTP requests and WebSocket messages")
2025-11-01 10:40:53 -04:00
// Wait for interrupt with proper cleanup
<-interrupt
2025-11-01 10:40:53 -04:00
logBoth("\n👋 Stopping monitor...")
// Close browser and context to stop all goroutines
if page != nil {
page.Close()
}
if context != nil {
context.Close()
}
if browser != nil {
browser.Close()
}
// Give goroutines time to finish
time.Sleep(500 * time.Millisecond)
logBoth("✅ Monitor stopped. Check %s for captured traffic", logFile)
}