//go:build sim package main import ( "encoding/json" "log" "net/url" "os" "os/signal" "syscall" "time" "github.com/gorilla/websocket" ) const ( gatewayURL = "ws://localhost:3333/sync" apiKey = "gateway" serverID = "test-server-001" ) type Handshake struct { Type string `json:"type"` Data json.RawMessage `json:"data"` } type ModHandshake struct { ServerID string `json:"server_id"` } type GatewayAck struct { Status string `json:"status"` Type string `json:"type"` } type User struct { ID string `json:"id"` Name string `json:"name"` } type Destination struct { ID string `json:"channel_id,omitempty"` } type GatewayMessageIn struct { ID string `json:"id"` // where am I from (channel_id or server_id) MsgID string `json:"msg_id"` // msg id Destination Destination `json:"destination,omitempty"` // where do I wanna go (channel_id or empty if from Bot) Author User `json:"author"` // who sent the message Content string `json:"content"` // message content Meta map[string]interface{} `json:"meta,omitempty"` // additional metadata Ts time.Time `json:"ts,omitempty"` // timestamp ReceivedAt time.Time `json:"-"` // ReceivedAt is populated by gateway (not from mod) } func main() { interrupt := make(chan os.Signal, 1) signal.Notify(interrupt, os.Interrupt, syscall.SIGTERM) // Build WebSocket URL with API key u, err := url.Parse(gatewayURL) if err != nil { log.Fatalf("Failed to parse URL: %v", err) } q := u.Query() q.Set("api_key", apiKey) u.RawQuery = q.Encode() log.Printf("Connecting to %s", u.String()) // Connect to WebSocket conn, _, err := websocket.DefaultDialer.Dial(u.String(), nil) if err != nil { log.Fatalf("Failed to connect: %v", err) } defer conn.Close() log.Println("Connected to gateway") // Set up ping handler - respond to pings from server conn.SetPingHandler(func(appData string) error { log.Println("Received ping from server, sending pong") err := conn.WriteControl(websocket.PongMessage, []byte(appData), time.Now().Add(5*time.Second)) if err != nil { log.Printf("Failed to send pong: %v", err) return err } return nil }) // Send handshake modHS := ModHandshake{ServerID: serverID} modHSData, err := json.Marshal(modHS) if err != nil { log.Fatalf("Failed to marshal mod handshake: %v", err) } handshake := Handshake{ Type: "mod", Data: modHSData, } if err := conn.WriteJSON(handshake); err != nil { log.Fatalf("Failed to send handshake: %v", err) } log.Println("Handshake sent") // Read acknowledgment var ack GatewayAck if err := conn.ReadJSON(&ack); err != nil { log.Fatalf("Failed to read acknowledgment: %v", err) } log.Printf("Received acknowledgment: status=%s, type=%s", ack.Status, ack.Type) // Channel for incoming messages done := make(chan struct{}) // Read loop - handles incoming messages and processes control frames go func() { defer close(done) for { messageType, message, err := conn.ReadMessage() if err != nil { if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseNormalClosure) { log.Printf("WebSocket error: %v", err) } else { log.Printf("Connection closed: %v", err) } return } // Only log text/binary messages (ping/pong handled by handlers) if messageType == websocket.TextMessage || messageType == websocket.BinaryMessage { log.Printf("Received from server: %s", string(message)) } } }() // Optional: Send a test message after connecting time.Sleep(2 * time.Second) testMsg := GatewayMessageIn{ MsgID: "test-msg-001", ID: serverID, Destination: Destination{ ID: "123456789", }, Author: User{ ID: "player-uuid-123", Name: "TestPlayer", }, Content: "Hello from simulated mod!", Ts: time.Now().UTC(), } if err := conn.WriteJSON(testMsg); err != nil { log.Printf("Failed to send test message: %v", err) } else { log.Println("Sent test message to gateway") } log.Println("Connection established. Responding to pings. Press Ctrl+C to disconnect.") // Wait for interrupt or connection close for { select { case <-done: log.Println("Connection closed") return case <-interrupt: log.Println("Interrupt received, closing connection...") // Send close message err := conn.WriteMessage( websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""), ) if err != nil { log.Printf("Write close error: %v", err) return } select { case <-done: case <-time.After(time.Second): } return } } }