diff --git a/bridge/jackbox/manager.go b/bridge/jackbox/manager.go index 34273bc..666d393 100644 --- a/bridge/jackbox/manager.go +++ b/bridge/jackbox/manager.go @@ -128,9 +128,20 @@ func (m *Manager) startWebSocketClient(messageCallback func(string)) error { // Get EnableRoomCodeImage setting from config (defaults to false) enableRoomCodeImage := m.config.Viper().GetBool("jackbox.EnableRoomCodeImage") - + + // Get configurable delays for room code broadcast + imageDelay := time.Duration(m.config.Viper().GetInt("jackbox.RoomCodeImageDelay")) * time.Second + plaintextDelay := time.Duration(m.config.Viper().GetInt("jackbox.RoomCodePlaintextDelay")) * time.Second + if plaintextDelay == 0 { + plaintextDelay = 29 * time.Second // default + } + m.log.Infof("Room code delays: imageDelay=%v, plaintextDelay=%v (raw config: image=%d, plaintext=%d)", + imageDelay, plaintextDelay, + m.config.Viper().GetInt("jackbox.RoomCodeImageDelay"), + m.config.Viper().GetInt("jackbox.RoomCodePlaintextDelay")) + // Create WebSocket client (pass the API client for vote tracking) - m.wsClient = NewWebSocketClient(apiURL, token, wrappedCallback, m.client, enableRoomCodeImage, m.log) + m.wsClient = NewWebSocketClient(apiURL, token, wrappedCallback, m.client, enableRoomCodeImage, imageDelay, plaintextDelay, m.log) // Connect to WebSocket if err := m.wsClient.Connect(); err != nil { diff --git a/bridge/jackbox/roomcode_image.go b/bridge/jackbox/roomcode_image.go index 3b5000c..2122f14 100644 --- a/bridge/jackbox/roomcode_image.go +++ b/bridge/jackbox/roomcode_image.go @@ -305,10 +305,10 @@ func GenerateRoomCodeImage(roomCode, gameTitle string) ([]byte, error) { } // Animation parameters - initialPauseFrames := 25 // Initial pause before animation starts (2.5 seconds at 10fps) - fadeFrames := 10 // Number of frames for fade-in (1 second at 10fps) - pauseFrames := 30 // Frames to pause between characters (3 seconds at 10fps) - frameDelay := 10 // 10/100 second = 0.1s per frame (10 fps) + initialPauseFrames := 1 // Initial pause before animation starts (2.5 seconds at 10fps) + fadeFrames := 10 // Number of frames for fade-in (1 second at 10fps) + pauseFrames := 30 // Frames to pause between characters (3 seconds at 10fps) + frameDelay := 10 // 10/100 second = 0.1s per frame (10 fps) // Helper function to draw a frame and convert to paletted drawFrame := func(charIndex int, fadeProgress float64) *image.Paletted { diff --git a/bridge/jackbox/websocket_client.go b/bridge/jackbox/websocket_client.go index db150c9..84ffe62 100644 --- a/bridge/jackbox/websocket_client.go +++ b/bridge/jackbox/websocket_client.go @@ -24,8 +24,10 @@ type WebSocketClient struct { stopChan chan struct{} connected bool authenticated bool - subscribedSession int - enableRoomCodeImage bool // Whether to upload room code images to Kosmi + subscribedSession int + enableRoomCodeImage bool // Whether to upload room code images to Kosmi + roomCodeImageDelay time.Duration // Delay before sending image announcement + roomCodePlaintextDelay time.Duration // Delay before sending plaintext room code } // WebSocket message types @@ -67,17 +69,19 @@ type GameAddedData struct { } // NewWebSocketClient creates a new WebSocket client -func NewWebSocketClient(apiURL, token string, messageCallback func(string), apiClient *Client, enableRoomCodeImage bool, log *logrus.Entry) *WebSocketClient { +func NewWebSocketClient(apiURL, token string, messageCallback func(string), apiClient *Client, enableRoomCodeImage bool, roomCodeImageDelay, roomCodePlaintextDelay time.Duration, log *logrus.Entry) *WebSocketClient { return &WebSocketClient{ - apiURL: apiURL, - token: token, - messageCallback: messageCallback, - apiClient: apiClient, - enableRoomCodeImage: enableRoomCodeImage, - log: log, - reconnectDelay: 1 * time.Second, - maxReconnect: 30 * time.Second, - stopChan: make(chan struct{}), + apiURL: apiURL, + token: token, + messageCallback: messageCallback, + apiClient: apiClient, + enableRoomCodeImage: enableRoomCodeImage, + roomCodeImageDelay: roomCodeImageDelay, + roomCodePlaintextDelay: roomCodePlaintextDelay, + log: log, + reconnectDelay: 1 * time.Second, + maxReconnect: 30 * time.Second, + stopChan: make(chan struct{}), } } @@ -93,7 +97,7 @@ func (c *WebSocketClient) Connect() error { } else if len(wsURL) > 8 && wsURL[:8] == "https://" { wsURL = "wss://" + wsURL[8:] } - + wsURL += "/api/sessions/live" c.log.Infof("Connecting to WebSocket: %s", wsURL) @@ -169,7 +173,7 @@ func (c *WebSocketClient) Unsubscribe(sessionID int) error { if c.subscribedSession == sessionID { c.subscribedSession = 0 } - + c.log.Infof("Unsubscribed from session %d", sessionID) return nil } @@ -327,7 +331,7 @@ func (c *WebSocketClient) handleGameAdded(data json.RawMessage) { // The message parameter should contain the full game announcement including any vote results func (c *WebSocketClient) broadcastWithRoomCodeImage(message, gameTitle, roomCode string) { c.log.Infof("šŸŽØ Starting room code image generation and upload for: %s - %s", gameTitle, roomCode) - + // Generate room code image (animated GIF) with game title embedded c.log.Infof("šŸ“ Step 1: Generating image...") imageData, err := GenerateRoomCodeImage(roomCode, gameTitle) @@ -360,6 +364,10 @@ func (c *WebSocketClient) broadcastWithRoomCodeImage(message, gameTitle, roomCod c.log.Infof("āœ… Step 2 complete: Uploaded to %s", imageURL) // Now that upload succeeded, send the full announcement with the message and URL + if c.roomCodeImageDelay > 0 { + c.log.Infof("ā³ Step 3: Waiting %v before broadcasting game announcement...", c.roomCodeImageDelay) + time.Sleep(c.roomCodeImageDelay) + } c.log.Infof("šŸ“¢ Step 3: Broadcasting game announcement with URL...") fullMessage := fmt.Sprintf("%s %s", message, imageURL) if c.messageCallback != nil { @@ -369,18 +377,19 @@ func (c *WebSocketClient) broadcastWithRoomCodeImage(message, gameTitle, roomCod c.log.Error("āŒ Step 3 failed: messageCallback is nil") } - // Send the plaintext room code after 19 seconds (to sync with animation completion) + // Send the plaintext room code after configured delay (to sync with animation completion) // Capture callback and logger in closure callback := c.messageCallback logger := c.log plainRoomCode := roomCode // Capture room code for plain text message - - c.log.Infof("ā° Step 4: Starting 19-second timer goroutine for plaintext room code...") + plaintextDelay := c.roomCodePlaintextDelay + + c.log.Infof("ā° Step 4: Starting %v timer goroutine for plaintext room code...", plaintextDelay) go func() { - logger.Infof("ā° [Goroutine started] Waiting 19 seconds before sending plaintext room code: %s", plainRoomCode) - time.Sleep(19 * time.Second) - logger.Infof("ā° [19 seconds elapsed] Now sending plaintext room code...") - + logger.Infof("ā° [Goroutine started] Waiting %v before sending plaintext room code: %s", plaintextDelay, plainRoomCode) + time.Sleep(plaintextDelay) + logger.Infof("ā° [%v elapsed] Now sending plaintext room code...", plaintextDelay) + if callback != nil { // Send just the room code in plaintext (for easy copy/paste) plaintextMessage := fmt.Sprintf("Room Code: %s", plainRoomCode) @@ -391,8 +400,8 @@ func (c *WebSocketClient) broadcastWithRoomCodeImage(message, gameTitle, roomCod logger.Error("āŒ Message callback is nil when trying to send delayed room code") } }() - - c.log.Infof("āœ… Step 4 complete: Goroutine launched, will fire in 19 seconds") + + c.log.Infof("āœ… Step 4 complete: Goroutine launched, will fire in %v", plaintextDelay) } // handleSessionEnded processes session.ended events @@ -409,14 +418,14 @@ func (c *WebSocketClient) AnnounceSessionEnd() { lastVote := c.apiClient.GetAndClearLastVoteResponse() if lastVote != nil { // Include final vote results - message = fmt.Sprintf("šŸ—³ļø Final votes for %s: %dšŸ‘ %dšŸ‘Ž (Score: %d)\nšŸŒ™ Game Night has ended! Thanks for playing!", + message = fmt.Sprintf("šŸ—³ļø Final votes for %s: %dšŸ‘ %dšŸ‘Ž (Score: %d)\nšŸŒ™ Game Night has ended! Thanks for playing!", lastVote.Game.Title, lastVote.Game.Upvotes, lastVote.Game.Downvotes, lastVote.Game.PopularityScore) } else { // No votes for final game message = "šŸŒ™ Game Night has ended! Thanks for playing!" } - + // Clear the active session c.apiClient.SetActiveSession(0) } else { @@ -486,10 +495,10 @@ func (c *WebSocketClient) handleDisconnect() { return case <-time.After(delay): c.log.Infof("Reconnecting... (delay: %v)", delay) - + if err := c.Connect(); err != nil { c.log.Errorf("Reconnection failed: %v", err) - + // Increase delay with exponential backoff delay *= 2 if delay > c.maxReconnect { @@ -500,14 +509,14 @@ func (c *WebSocketClient) handleDisconnect() { // Reconnected successfully c.log.Info("Reconnected successfully") - + // Re-subscribe if we were subscribed before if c.subscribedSession > 0 { if err := c.Subscribe(c.subscribedSession); err != nil { c.log.Errorf("Failed to re-subscribe: %v", err) } } - + return } } @@ -545,4 +554,3 @@ func (c *WebSocketClient) IsSubscribed() bool { defer c.mu.Unlock() return c.subscribedSession > 0 } -