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
|
Sneedchat-Discord-Bridge
|
||||||
file.log
|
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
|
## Troubleshooting
|
||||||
|
|
||||||
### Cookie Issues
|
### 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()
|
OnConnect func()
|
||||||
OnDisconnect 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{}
|
recentOutboundIter func() []map[string]interface{}
|
||||||
mapDiscordSneed func(string, int, string)
|
mapDiscordSneed func(string, int, string)
|
||||||
|
|
||||||
@@ -197,6 +202,10 @@ func (c *Client) readLoop(ctx context.Context) {
|
|||||||
|
|
||||||
raw := string(message)
|
raw := string(message)
|
||||||
|
|
||||||
|
if c.OnRaw != nil {
|
||||||
|
c.OnRaw(raw)
|
||||||
|
}
|
||||||
|
|
||||||
// Server sends plaintext "cannot join" when session has expired.
|
// Server sends plaintext "cannot join" when session has expired.
|
||||||
if isCannotJoin(raw) {
|
if isCannotJoin(raw) {
|
||||||
log.Println("⚠️ 'cannot join' received — refreshing session and reconnecting...")
|
log.Println("⚠️ 'cannot join' received — refreshing session and reconnecting...")
|
||||||
|
|||||||
Reference in New Issue
Block a user