Merge pull request #5 from Salastil/claude/websocket-debug-tool-pm4m6d
Add wsdebug tool for WebSocket frame inspection
This commit is contained in:
@@ -1,2 +1,3 @@
|
||||
Sneedchat-Discord-Bridge
|
||||
file.log
|
||||
wsdebug.log
|
||||
|
||||
@@ -245,6 +245,31 @@ sudo systemctl status sneedchat-bridge
|
||||
|
||||
---
|
||||
|
||||
## Debugging Tools
|
||||
|
||||
### wsdebug — standalone WebSocket inspector
|
||||
|
||||
`wsdebug` is a separate binary that logs into Kiwi Farms using the same `.env` file as the bridge, connects directly to the Sneedchat WebSocket, and prints every raw frame it receives to stdout while also writing it to a log file. It doesn't touch Discord at all — use it to see exactly what Sneedchat is sending, independent of the bridge.
|
||||
|
||||
Build it:
|
||||
|
||||
```bash
|
||||
go build -o wsdebug ./cmd/wsdebug
|
||||
```
|
||||
|
||||
Run it:
|
||||
|
||||
```bash
|
||||
./wsdebug --env .env --log wsdebug.log --room 1
|
||||
```
|
||||
|
||||
Flags:
|
||||
- `--env` - path to the `.env` file to load (default `.env`)
|
||||
- `--log` - path to the log file to append output to (default `wsdebug.log`)
|
||||
- `--room` - override `SNEEDCHAT_ROOM_ID` from the `.env` file (optional)
|
||||
|
||||
Press `Ctrl+C` to stop. Every raw WebSocket frame, plus connect/disconnect/message/edit/delete events, are printed to the terminal and appended to the log file simultaneously.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Cookie Issues
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
// Command wsdebug is a standalone debugging tool that logs into Kiwi Farms
|
||||
// using the same .env credentials as the bridge, opens the Sneedchat
|
||||
// WebSocket connection, and prints/logs every raw frame received. It does
|
||||
// not touch Discord at all — it exists purely to inspect what the
|
||||
// WebSocket is sending.
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"local/sneedchatbridge/config"
|
||||
"local/sneedchatbridge/cookie"
|
||||
"local/sneedchatbridge/sneed"
|
||||
)
|
||||
|
||||
func main() {
|
||||
envFile := flag.String("env", ".env", "path to .env file")
|
||||
logFile := flag.String("log", "wsdebug.log", "path to log file")
|
||||
room := flag.Int("room", 0, "override SNEEDCHAT_ROOM_ID from .env")
|
||||
flag.Parse()
|
||||
|
||||
cfg, err := config.Load(*envFile)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to load config: %v", err)
|
||||
}
|
||||
if *room != 0 {
|
||||
cfg.SneedchatRoomID = *room
|
||||
}
|
||||
|
||||
f, err := os.OpenFile(*logFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to open log file %s: %v", *logFile, err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
logger := log.New(io.MultiWriter(os.Stdout, f), "", log.LstdFlags|log.Lmicroseconds)
|
||||
log.SetOutput(io.MultiWriter(os.Stdout, f))
|
||||
log.SetFlags(log.LstdFlags | log.Lmicroseconds)
|
||||
|
||||
logger.Printf("wsdebug starting — env=%s log=%s room=%d", *envFile, *logFile, cfg.SneedchatRoomID)
|
||||
|
||||
ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
|
||||
defer cancel()
|
||||
|
||||
session, err := cookie.NewSessionService(ctx, "kiwifarms.st", cfg.BridgeUsername, cfg.BridgePassword)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to establish session: %v", err)
|
||||
}
|
||||
defer session.Close()
|
||||
|
||||
client := sneed.NewClient(cfg.SneedchatRoomID, session, true)
|
||||
client.SetBridgeIdentity(cfg.BridgeUserID, cfg.BridgeUsername)
|
||||
|
||||
client.OnRaw = func(raw string) {
|
||||
logger.Printf("RECV: %s", raw)
|
||||
}
|
||||
client.OnConnect = func() {
|
||||
logger.Printf("=== CONNECTED (room %d) ===", cfg.SneedchatRoomID)
|
||||
}
|
||||
client.OnDisconnect = func() {
|
||||
logger.Printf("=== DISCONNECTED ===")
|
||||
}
|
||||
client.OnMessage = func(m map[string]interface{}) {
|
||||
logger.Printf("MESSAGE: user=%v content=%q uuid=%v", m["username"], m["content"], m["message_uuid"])
|
||||
}
|
||||
client.OnEdit = func(uuid, content string) {
|
||||
logger.Printf("EDIT: uuid=%s content=%q", uuid, content)
|
||||
}
|
||||
client.OnDelete = func(uuid string) {
|
||||
logger.Printf("DELETE: uuid=%s", uuid)
|
||||
}
|
||||
|
||||
if err := client.Connect(ctx); err != nil {
|
||||
log.Fatalf("Initial connect failed: %v", err)
|
||||
}
|
||||
|
||||
logger.Println("wsdebug running — press Ctrl+C to stop")
|
||||
|
||||
<-ctx.Done()
|
||||
stopTime := time.Now().Format(time.RFC3339)
|
||||
fmt.Fprintf(f, "--- shutdown requested at %s ---\n", stopTime)
|
||||
logger.Println("Shutdown signal received, disconnecting...")
|
||||
client.Disconnect()
|
||||
logger.Println("wsdebug stopped")
|
||||
}
|
||||
@@ -63,6 +63,11 @@ type Client struct {
|
||||
OnConnect func()
|
||||
OnDisconnect func()
|
||||
|
||||
// OnRaw, if set, is called with every raw WebSocket frame exactly as
|
||||
// received, before any parsing or truncation. Intended for debugging
|
||||
// tools that need to observe the unfiltered stream.
|
||||
OnRaw func(string)
|
||||
|
||||
recentOutboundIter func() []map[string]interface{}
|
||||
mapDiscordSneed func(string, int, string)
|
||||
|
||||
@@ -197,6 +202,10 @@ func (c *Client) readLoop(ctx context.Context) {
|
||||
|
||||
raw := string(message)
|
||||
|
||||
if c.OnRaw != nil {
|
||||
c.OnRaw(raw)
|
||||
}
|
||||
|
||||
// Server sends plaintext "cannot join" when session has expired.
|
||||
if isCannotJoin(raw) {
|
||||
log.Println("⚠️ 'cannot join' received — refreshing session and reconnecting...")
|
||||
|
||||
Reference in New Issue
Block a user