feat: announce poll lifecycle events to IRC and Kosmi
Handle poll.start, poll.ending, voting.ended, and poll.ending.cancelled WebSocket messages from the upstream GamePicker API. Broadcasts opening, closing-countdown, and closed announcements with per-bridge bold formatting (IRC control codes vs asterisks for Kosmi). Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -17,23 +17,24 @@ const (
|
||||
|
||||
// WebSocketClient handles WebSocket connection to Jackbox API
|
||||
type WebSocketClient struct {
|
||||
apiURL string
|
||||
token string
|
||||
conn *websocket.Conn
|
||||
messageCallback func(string)
|
||||
apiClient *Client // Reference to API client for vote tracking
|
||||
log *logrus.Entry
|
||||
mu sync.Mutex
|
||||
reconnectDelay time.Duration
|
||||
maxReconnect time.Duration
|
||||
stopChan chan struct{}
|
||||
listenDone chan struct{} // closed when the current listen() goroutine exits
|
||||
connected bool
|
||||
authenticated bool
|
||||
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
|
||||
apiURL string
|
||||
token string
|
||||
conn *websocket.Conn
|
||||
messageCallback func(string)
|
||||
formattedMessageCallback func(string, string) // (ircMsg, plainMsg)
|
||||
apiClient *Client // Reference to API client for vote tracking
|
||||
log *logrus.Entry
|
||||
mu sync.Mutex
|
||||
reconnectDelay time.Duration
|
||||
maxReconnect time.Duration
|
||||
stopChan chan struct{}
|
||||
listenDone chan struct{} // closed when the current listen() goroutine exits
|
||||
connected bool
|
||||
authenticated bool
|
||||
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
|
||||
@@ -74,20 +75,28 @@ type GameAddedData struct {
|
||||
} `json:"game"`
|
||||
}
|
||||
|
||||
// PollEndingData represents the poll.ending event data
|
||||
type PollEndingData struct {
|
||||
SessionID int `json:"sessionId"`
|
||||
EndsAt string `json:"endsAt"`
|
||||
DelaySeconds int `json:"delaySeconds"`
|
||||
}
|
||||
|
||||
// NewWebSocketClient creates a new WebSocket client
|
||||
func NewWebSocketClient(apiURL, token string, messageCallback func(string), apiClient *Client, enableRoomCodeImage bool, roomCodeImageDelay, roomCodePlaintextDelay time.Duration, log *logrus.Entry) *WebSocketClient {
|
||||
func NewWebSocketClient(apiURL, token string, messageCallback func(string), formattedMessageCallback func(string, 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,
|
||||
roomCodeImageDelay: roomCodeImageDelay,
|
||||
roomCodePlaintextDelay: roomCodePlaintextDelay,
|
||||
log: log,
|
||||
reconnectDelay: 1 * time.Second,
|
||||
maxReconnect: 30 * time.Second,
|
||||
stopChan: make(chan struct{}),
|
||||
apiURL: apiURL,
|
||||
token: token,
|
||||
messageCallback: messageCallback,
|
||||
formattedMessageCallback: formattedMessageCallback,
|
||||
apiClient: apiClient,
|
||||
enableRoomCodeImage: enableRoomCodeImage,
|
||||
roomCodeImageDelay: roomCodeImageDelay,
|
||||
roomCodePlaintextDelay: roomCodePlaintextDelay,
|
||||
log: log,
|
||||
reconnectDelay: 1 * time.Second,
|
||||
maxReconnect: 30 * time.Second,
|
||||
stopChan: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -249,6 +258,18 @@ func (c *WebSocketClient) handleMessage(data []byte) {
|
||||
case "session.ended":
|
||||
c.handleSessionEnded(msg.Data)
|
||||
|
||||
case "poll.start":
|
||||
c.handlePollStart()
|
||||
|
||||
case "poll.ending":
|
||||
c.handlePollEnding(msg.Data)
|
||||
|
||||
case "voting.ended":
|
||||
c.handleVotingEnded()
|
||||
|
||||
case "poll.ending.cancelled":
|
||||
c.log.Info("Poll ending cancelled, voting remains open")
|
||||
|
||||
case "pong":
|
||||
c.log.Debug("Heartbeat pong received")
|
||||
|
||||
@@ -450,6 +471,46 @@ func (c *WebSocketClient) AnnounceSessionEnd() {
|
||||
}
|
||||
}
|
||||
|
||||
// handlePollStart announces that a new poll has opened
|
||||
func (c *WebSocketClient) handlePollStart() {
|
||||
c.log.Info("Poll started")
|
||||
|
||||
ircMsg := "🗳️ There is a new poll open! Your vote is \x02REQUIRED\x02 https://hso.ehsf.cc/vote"
|
||||
plainMsg := "🗳️ There is a new poll open! Your vote is *REQUIRED* https://hso.ehsf.cc/vote"
|
||||
|
||||
if c.formattedMessageCallback != nil {
|
||||
c.formattedMessageCallback(ircMsg, plainMsg)
|
||||
}
|
||||
}
|
||||
|
||||
// handlePollEnding announces that the poll is about to close
|
||||
func (c *WebSocketClient) handlePollEnding(data json.RawMessage) {
|
||||
var pollData PollEndingData
|
||||
if err := json.Unmarshal(data, &pollData); err != nil {
|
||||
c.log.Errorf("Failed to parse poll.ending data: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
c.log.Infof("Poll ending in %d seconds", pollData.DelaySeconds)
|
||||
|
||||
ircMsg := fmt.Sprintf("🗳️ \x02HURRY!\x02 https://hso.ehsf.cc/vote Go vote now, the poll is closing in %d seconds", pollData.DelaySeconds)
|
||||
plainMsg := fmt.Sprintf("🗳️ *HURRY!* https://hso.ehsf.cc/vote Go vote now, the poll is closing in %d seconds", pollData.DelaySeconds)
|
||||
|
||||
if c.formattedMessageCallback != nil {
|
||||
c.formattedMessageCallback(ircMsg, plainMsg)
|
||||
}
|
||||
}
|
||||
|
||||
// handleVotingEnded announces that the poll has closed
|
||||
func (c *WebSocketClient) handleVotingEnded() {
|
||||
c.log.Info("Voting ended")
|
||||
|
||||
message := "🗳️ You missed your chance to vote, do your duty next time"
|
||||
if c.messageCallback != nil {
|
||||
c.messageCallback(message)
|
||||
}
|
||||
}
|
||||
|
||||
// startHeartbeat sends ping messages periodically. It exits when listenDone
|
||||
// is closed (current connection ended) or stopChan is closed (full shutdown).
|
||||
func (c *WebSocketClient) startHeartbeat(listenDone <-chan struct{}) {
|
||||
|
||||
Reference in New Issue
Block a user