const { getPokerService } = require("../utils/crumbPoker"); const { refreshPokerTableMessage } = require("../utils/pokerDiscord"); const { announcePokerHandProgress, createPokerProfileContext, recordPokerHandProfiles } = require("../utils/pokerProfile"); const TURN_CHECK_INTERVAL = 5 * 1000; const LOBBY_CHECK_INTERVAL = 30 * 1000; const STARTUP_RECOVERY_DELAY = 10 * 1000; module.exports = client => { setTimeout( () => { recoverPoker( client ); }, STARTUP_RECOVERY_DELAY ); setInterval( () => { processPokerTurns( client ); }, TURN_CHECK_INTERVAL ); setInterval( () => { processPokerLobbies( client ); }, LOBBY_CHECK_INTERVAL ); console.log( "[Poker] Recovery and timeout watcher initialized" ); }; async function refreshResultGame( client, result ) { const game = result?.game; if (!game) { return; } await refreshPokerTableMessage( client, game ); } async function processPokerTurns( client ) { if (!client.isReady()) { return; } try { const results = await getPokerService() .processExpiredTurns(); for (const result of results) { if (result.processed) { console.log( `[Poker] Auto-folded ${result.userId} after turn timeout.` ); await refreshResultGame( client, result ); if ( result.game ?.data ?.hand ?.state === "PAYOUT" ) { try { await recordPokerHandProfiles( result.game ); const context = await createPokerProfileContext( client, result.game ); if (context) { await announcePokerHandProgress( context, result.game ); } } catch (error) { console.error( `[PokerProfile] Timeout profile processing failed for ${result.game.gameId}:`, error ); } } } } } catch (error) { console.error( "[Poker] Turn watcher error:", error ); } } async function processPokerLobbies( client ) { if (!client.isReady()) { return; } try { const results = await getPokerService() .processExpiredLobbies(); for (const result of results) { if (result.expired) { console.log( `[Poker] Expired waiting Poker table ${result.game?.gameId ?? "unknown"}.` ); await refreshResultGame( client, result ); } } } catch (error) { console.error( "[Poker] Lobby watcher error:", error ); } } async function recoverPoker( client ) { if (!client.isReady()) { return; } try { const service = getPokerService(); const result = await service.recover(); const activeTables = await service.listActiveTables(); /* * Rehydrate every still-active public Poker table after * restart, even when no recovery action was necessary. * Mongo remains authoritative; the Discord embed is only * a projection of the current stored game state. */ for (const game of activeTables) { try { await refreshPokerTableMessage( client, game ); if ( game.data?.hand?.state === "PAYOUT" ) { await recordPokerHandProfiles( game ); const context = await createPokerProfileContext( client, game ); if (context) { await announcePokerHandProgress( context, game ); } } } catch (error) { console.error( `[Poker] Failed to refresh active table ${game.gameId}:`, error ); } } for ( const turn of result.turns ?? [] ) { if (turn.processed) { await refreshResultGame( client, turn ); } } for ( const lobby of result.lobbies ?? [] ) { if (lobby.expired) { await refreshResultGame( client, lobby ); } } for ( const closing of result.closingTables ?? [] ) { await refreshResultGame( client, closing ); } for ( const cashout of result.cashouts ?? [] ) { try { const game = await service.getTable({ gameId: cashout.gameId }); await refreshPokerTableMessage( client, game ); } catch {} } console.log( `[Poker] Recovery complete: ` + `${result.memberships.length} membership(s), ` + `${result.cashouts.length} cash-out(s), ` + `${result.closingTables.length} closing table(s), ` + `${result.turns.length} expired turn(s), ` + `${result.lobbies.length} expired lobby/lobbies, ` + `${activeTables.length} active table(s) refreshed.` ); } catch (error) { console.error( "[Poker] Startup recovery error:", error ); } }