wow that took awhile
This commit is contained in:
277
bridge/jackbox/manager.go
Normal file
277
bridge/jackbox/manager.go
Normal file
@@ -0,0 +1,277 @@
|
||||
package jackbox
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/42wim/matterbridge/bridge/config"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// Manager handles the Jackbox integration lifecycle
|
||||
type Manager struct {
|
||||
client *Client
|
||||
webhookServer *WebhookServer
|
||||
wsClient *WebSocketClient
|
||||
config config.Config
|
||||
log *logrus.Entry
|
||||
enabled bool
|
||||
useWebSocket bool
|
||||
messageCallback func(string)
|
||||
muted bool
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
// NewManager creates a new Jackbox manager
|
||||
func NewManager(cfg config.Config, log *logrus.Entry) *Manager {
|
||||
return &Manager{
|
||||
config: cfg,
|
||||
log: log,
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize sets up the Jackbox client and webhook server or WebSocket client
|
||||
func (m *Manager) Initialize() error {
|
||||
// Check if Jackbox integration is enabled
|
||||
m.enabled = m.config.Viper().GetBool("jackbox.Enabled")
|
||||
if !m.enabled {
|
||||
m.log.Info("Jackbox integration is disabled")
|
||||
return nil
|
||||
}
|
||||
|
||||
m.log.Info("Initializing Jackbox integration...")
|
||||
|
||||
// Get configuration values
|
||||
apiURL := m.config.Viper().GetString("jackbox.APIURL")
|
||||
adminPassword := m.config.Viper().GetString("jackbox.AdminPassword")
|
||||
m.useWebSocket = m.config.Viper().GetBool("jackbox.UseWebSocket")
|
||||
|
||||
// Validate configuration
|
||||
if apiURL == "" {
|
||||
return fmt.Errorf("jackbox.APIURL is required when Jackbox integration is enabled")
|
||||
}
|
||||
if adminPassword == "" {
|
||||
return fmt.Errorf("jackbox.AdminPassword is required when Jackbox integration is enabled")
|
||||
}
|
||||
|
||||
// Create Jackbox API client
|
||||
m.client = NewClient(apiURL, adminPassword, m.log)
|
||||
|
||||
// Authenticate with the API
|
||||
if err := m.client.Authenticate(); err != nil {
|
||||
return fmt.Errorf("failed to authenticate with Jackbox API: %w", err)
|
||||
}
|
||||
|
||||
m.log.Info("Jackbox integration initialized successfully")
|
||||
return nil
|
||||
}
|
||||
|
||||
// StartWebhookServer starts the webhook server with the provided message callback
|
||||
func (m *Manager) StartWebhookServer(messageCallback func(string)) error {
|
||||
if !m.enabled {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Use WebSocket if enabled, otherwise fall back to webhook
|
||||
if m.useWebSocket {
|
||||
return m.startWebSocketClient(messageCallback)
|
||||
}
|
||||
|
||||
webhookPort := m.config.Viper().GetInt("jackbox.WebhookPort")
|
||||
webhookSecret := m.config.Viper().GetString("jackbox.WebhookSecret")
|
||||
|
||||
if webhookSecret == "" {
|
||||
return fmt.Errorf("jackbox.WebhookSecret is required when using webhooks")
|
||||
}
|
||||
if webhookPort == 0 {
|
||||
webhookPort = 3001
|
||||
}
|
||||
|
||||
// Wrap the callback to check mute status
|
||||
wrappedCallback := func(message string) {
|
||||
if m.IsMuted() {
|
||||
m.log.Debugf("Jackbox message suppressed (muted): %s", message)
|
||||
return
|
||||
}
|
||||
messageCallback(message)
|
||||
}
|
||||
|
||||
m.webhookServer = NewWebhookServer(webhookPort, webhookSecret, wrappedCallback, m.log)
|
||||
return m.webhookServer.Start()
|
||||
}
|
||||
|
||||
// startWebSocketClient starts the WebSocket client connection
|
||||
func (m *Manager) startWebSocketClient(messageCallback func(string)) error {
|
||||
apiURL := m.config.Viper().GetString("jackbox.APIURL")
|
||||
|
||||
// Store the callback for use in monitoring
|
||||
m.messageCallback = messageCallback
|
||||
|
||||
// Wrap the callback to check mute status
|
||||
wrappedCallback := func(message string) {
|
||||
if m.IsMuted() {
|
||||
m.log.Debugf("Jackbox message suppressed (muted): %s", message)
|
||||
return
|
||||
}
|
||||
messageCallback(message)
|
||||
}
|
||||
|
||||
// Set wrapped callback on client for vote broadcasts
|
||||
m.client.SetMessageCallback(wrappedCallback)
|
||||
|
||||
// Get JWT token from client
|
||||
token := m.client.GetToken()
|
||||
if token == "" {
|
||||
return fmt.Errorf("no JWT token available, authentication may have failed")
|
||||
}
|
||||
|
||||
// Get EnableRoomCodeImage setting from config (defaults to false)
|
||||
enableRoomCodeImage := m.config.Viper().GetBool("jackbox.EnableRoomCodeImage")
|
||||
|
||||
// Create WebSocket client (pass the API client for vote tracking)
|
||||
m.wsClient = NewWebSocketClient(apiURL, token, wrappedCallback, m.client, enableRoomCodeImage, m.log)
|
||||
|
||||
// Connect to WebSocket
|
||||
if err := m.wsClient.Connect(); err != nil {
|
||||
return fmt.Errorf("failed to connect WebSocket: %w", err)
|
||||
}
|
||||
|
||||
// Get active session and subscribe
|
||||
session, err := m.client.GetActiveSession()
|
||||
if err != nil {
|
||||
m.log.Warnf("Could not get active session: %v", err)
|
||||
m.log.Info("WebSocket connected but not subscribed to any session yet")
|
||||
return nil
|
||||
}
|
||||
|
||||
if session != nil && session.ID > 0 {
|
||||
if err := m.wsClient.Subscribe(session.ID); err != nil {
|
||||
m.log.Warnf("Failed to subscribe to session %d: %v", session.ID, err)
|
||||
}
|
||||
|
||||
// Set the active session on the client for vote tracking
|
||||
m.client.SetActiveSession(session.ID)
|
||||
|
||||
// Only announce if this is a NEW session (no games played yet)
|
||||
// If games have been played, the bot is just reconnecting to an existing session
|
||||
if session.GamesPlayed == 0 {
|
||||
announcement := fmt.Sprintf("🎮 Game Night is starting! Session #%d", session.ID)
|
||||
if messageCallback != nil {
|
||||
messageCallback(announcement)
|
||||
}
|
||||
} else {
|
||||
m.log.Infof("Reconnected to existing session #%d (%d games already played)", session.ID, session.GamesPlayed)
|
||||
}
|
||||
} else {
|
||||
m.log.Info("No active session found, will subscribe when session becomes active")
|
||||
// Start a goroutine to periodically check for active sessions
|
||||
go m.monitorActiveSessions()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// monitorActiveSessions periodically checks for active sessions and subscribes
|
||||
// This is a FALLBACK mechanism - only used when WebSocket events aren't working
|
||||
// Normally, session.started and session.ended events should handle this
|
||||
func (m *Manager) monitorActiveSessions() {
|
||||
ticker := time.NewTicker(30 * time.Second)
|
||||
defer ticker.Stop()
|
||||
|
||||
wasSubscribed := false
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
// Skip polling if WebSocket is disconnected (no point in polling)
|
||||
if m.wsClient == nil || !m.wsClient.IsConnected() {
|
||||
m.log.Debug("WebSocket disconnected, skipping session poll")
|
||||
continue
|
||||
}
|
||||
|
||||
session, err := m.client.GetActiveSession()
|
||||
if err != nil {
|
||||
m.log.Debugf("Error checking for active session: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
isSubscribed := m.wsClient.IsSubscribed()
|
||||
|
||||
// Check if we need to subscribe to a new session (fallback if session.started wasn't received)
|
||||
if !isSubscribed && session != nil && session.ID > 0 {
|
||||
m.log.Warnf("Found active session %d via polling (session.started event may have been missed), subscribing...", session.ID)
|
||||
if err := m.wsClient.Subscribe(session.ID); err != nil {
|
||||
m.log.Warnf("Failed to subscribe to session %d: %v", session.ID, err)
|
||||
} else {
|
||||
m.client.SetActiveSession(session.ID)
|
||||
wasSubscribed = true
|
||||
|
||||
// Only announce if this is a NEW session (no games played yet)
|
||||
if session.GamesPlayed == 0 && !m.IsMuted() {
|
||||
announcement := fmt.Sprintf("🎮 Game Night is starting! Session #%d", session.ID)
|
||||
if m.messageCallback != nil {
|
||||
m.messageCallback(announcement)
|
||||
}
|
||||
} else if session.GamesPlayed == 0 && m.IsMuted() {
|
||||
m.log.Debugf("Jackbox message suppressed (muted): 🎮 Game Night is starting! Session #%d", session.ID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if session ended (fallback if session.ended wasn't received)
|
||||
if wasSubscribed && (session == nil || session.ID == 0 || session.IsActive == 0) {
|
||||
m.log.Warn("Active session ended (detected via polling, session.ended event may have been missed)")
|
||||
if m.wsClient != nil {
|
||||
m.wsClient.AnnounceSessionEnd()
|
||||
}
|
||||
wasSubscribed = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GetClient returns the Jackbox API client (may be nil if disabled)
|
||||
func (m *Manager) GetClient() *Client {
|
||||
return m.client
|
||||
}
|
||||
|
||||
// IsEnabled returns whether Jackbox integration is enabled
|
||||
func (m *Manager) IsEnabled() bool {
|
||||
return m.enabled
|
||||
}
|
||||
|
||||
// Shutdown stops the webhook server or WebSocket client
|
||||
func (m *Manager) Shutdown() error {
|
||||
if m.wsClient != nil {
|
||||
if err := m.wsClient.Close(); err != nil {
|
||||
m.log.Errorf("Error closing WebSocket client: %v", err)
|
||||
}
|
||||
}
|
||||
if m.webhookServer != nil {
|
||||
return m.webhookServer.Stop()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetMuted sets the mute state for Jackbox announcements
|
||||
func (m *Manager) SetMuted(muted bool) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
m.muted = muted
|
||||
}
|
||||
|
||||
// ToggleMuted toggles the mute state and returns the new state
|
||||
func (m *Manager) ToggleMuted() bool {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
m.muted = !m.muted
|
||||
return m.muted
|
||||
}
|
||||
|
||||
// IsMuted returns the current mute state
|
||||
func (m *Manager) IsMuted() bool {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
return m.muted
|
||||
}
|
||||
Reference in New Issue
Block a user