import 'dotenv/config'; export function getConfig() { const token = process.env.DISCORD_TOKEN?.trim(); const guildId = process.env.GUILD_ID?.trim() || null; const databaseUrl = process.env.DATABASE_URL?.trim(); const port = Number(process.env.PORT || 7860); const adminRoleName = process.env.ADMIN_ROLE_NAME?.trim() || 'Admin'; const oddsApiKey = process.env.ODDS_API_KEY?.trim() || null; const oddsApiBaseUrl = process.env.ODDS_API_BASE_URL?.trim() || 'https://api.the-odds-api.com/v4'; const oddsApiSportKey = process.env.ODDS_API_SPORT_KEY?.trim() || 'baseball_mlb'; const oddsApiRegions = process.env.ODDS_API_REGIONS?.trim() || 'us,us2,us_dfs,us_ex'; const oddsApiBookmakers = (process.env.ODDS_API_BOOKMAKERS?.trim() || 'fanduel,draftkings,betmgm,williamhill_us,fanatics,ballybet,hardrockbet,betr_us_dfs,pick6,prizepicks,underdog,kalshi,novig,polymarket,prophetx') .split(',') .map((value) => value.trim()) .filter(Boolean); const oddsApiMarkets = (process.env.ODDS_API_MARKETS?.trim() || 'batter_home_runs,batter_hits,batter_total_bases,batter_rbis,batter_runs_scored,batter_hits_runs_rbis,pitcher_strikeouts') .split(',') .map((value) => value.trim()) .filter(Boolean); const circaDropboxUrl = process.env.CIRCA_DROPBOX_URL?.trim() || null; const scanReportChannelId = process.env.SCAN_REPORT_CHANNEL_ID?.trim() || null; const scanAlertChannelId = process.env.SCAN_ALERT_CHANNEL_ID?.trim() || null; const scanMorningTime = process.env.SCAN_MORNING_TIME?.trim() || '08:00'; const scanMorningFallbackTime = process.env.SCAN_MORNING_FALLBACK_TIME?.trim() || '09:45'; const scanTimeZone = process.env.SCAN_TIMEZONE?.trim() || 'America/Chicago'; const scanMinBooks = Number(process.env.SCAN_MIN_BOOKS || 6); const scanDisagreementThreshold = Number(process.env.SCAN_DISAGREEMENT_THRESHOLD || 0.08); const scanAlertCooldownMinutes = Number(process.env.SCAN_ALERT_COOLDOWN_MINUTES || 180); const scanFrequencyMinutes = Number(process.env.SCAN_FREQUENCY_MINUTES || 15); const scanHttpTimeoutMs = Number(process.env.SCAN_HTTP_TIMEOUT_MS || 15000); const directOddsCacheTtlMs = Number(process.env.DIRECT_ODDS_CACHE_TTL_MS || 60000); const sharpEdgeAlertThreshold = Number(process.env.SHARP_EDGE_ALERT_THRESHOLD || 0.04); const sharpEdgeAlertMinBooks = Number(process.env.SHARP_EDGE_ALERT_MIN_BOOKS || 6); const staleBookAlertThreshold = Number(process.env.STALE_BOOK_ALERT_THRESHOLD || 0.025); const reverseAlertThreshold = Number(process.env.REVERSE_ALERT_THRESHOLD || 0.03); const dfsScanMinBooks = Number(process.env.DFS_SCAN_MIN_BOOKS || 4); const dfsEdgeAlertThreshold = Number(process.env.DFS_EDGE_ALERT_THRESHOLD || sharpEdgeAlertThreshold); const dfsReverseAlertThreshold = Number(process.env.DFS_REVERSE_ALERT_THRESHOLD || reverseAlertThreshold); const dfsStaleAlertThreshold = Number(process.env.DFS_STALE_ALERT_THRESHOLD || staleBookAlertThreshold); const exchangeScanMinBooks = Number(process.env.EXCHANGE_SCAN_MIN_BOOKS || 4); const exchangeEdgeAlertThreshold = Number(process.env.EXCHANGE_EDGE_ALERT_THRESHOLD || sharpEdgeAlertThreshold); const exchangeReverseAlertThreshold = Number(process.env.EXCHANGE_REVERSE_ALERT_THRESHOLD || reverseAlertThreshold); const exchangeStaleAlertThreshold = Number(process.env.EXCHANGE_STALE_ALERT_THRESHOLD || staleBookAlertThreshold); const marketBoardDefaultLimit = Number(process.env.MARKET_BOARD_DEFAULT_LIMIT || 10); const sharpEdgeAlertsEnabled = process.env.SHARP_EDGE_ALERTS_ENABLED?.trim() !== 'false'; const staleBookAlertsEnabled = process.env.STALE_BOOK_ALERTS_ENABLED?.trim() !== 'false'; const reverseAlertsEnabled = process.env.REVERSE_ALERTS_ENABLED?.trim() !== 'false'; const circaChannelId = process.env.CIRCA_CHANNEL_ID?.trim() || null; const circaDailyTime = process.env.CIRCA_DAILY_TIME?.trim() || '09:30'; const circaTimeZone = process.env.CIRCA_TIMEZONE?.trim() || 'America/Chicago'; const circaRetryMinutes = Number(process.env.CIRCA_RETRY_MINUTES || 30); const circaMovementFrequencyMinutes = Number(process.env.CIRCA_MOVEMENT_FREQUENCY_MINUTES || 5); const matchupHostedBaseUrl = process.env.MLB_HOSTED_BASE_URL?.trim() || null; const matchupArtifactCacheTtlMs = Number(process.env.MATCHUP_ARTIFACT_CACHE_TTL_MS || 300000); const matchupFallbackDays = Number(process.env.MATCHUP_FALLBACK_DAYS || 7); if (!token) { throw new Error('Missing DISCORD_TOKEN in environment.'); } if (!databaseUrl) { throw new Error('Missing DATABASE_URL in environment.'); } return { token, guildId, databaseUrl, port, adminRoleName, matchups: { enabled: Boolean(matchupHostedBaseUrl || databaseUrl), hosted: { baseUrl: matchupHostedBaseUrl, cacheTtlMs: matchupArtifactCacheTtlMs, fallbackDays: matchupFallbackDays, }, databaseUrl, }, scanner: { enabled: Boolean( circaDropboxUrl && circaChannelId || oddsApiKey && scanReportChannelId && scanAlertChannelId && circaDropboxUrl ), oddsWorkflowEnabled: Boolean( oddsApiKey && scanReportChannelId && scanAlertChannelId && circaDropboxUrl ), circaWorkflowEnabled: Boolean(circaDropboxUrl && circaChannelId), oddsApiKey, oddsApiBaseUrl, oddsApiSportKey, oddsApiRegions, oddsApiBookmakers, oddsApiMarkets, circaDropboxUrl, scanReportChannelId, scanAlertChannelId, scanMorningTime, scanMorningFallbackTime, scanTimeZone, scanMinBooks, scanDisagreementThreshold, scanAlertCooldownMinutes, scanFrequencyMinutes, scanHttpTimeoutMs, directOddsCacheTtlMs, sharpEdgeAlertThreshold, sharpEdgeAlertMinBooks, staleBookAlertThreshold, reverseAlertThreshold, dfsScanMinBooks, dfsEdgeAlertThreshold, dfsReverseAlertThreshold, dfsStaleAlertThreshold, exchangeScanMinBooks, exchangeEdgeAlertThreshold, exchangeReverseAlertThreshold, exchangeStaleAlertThreshold, marketBoardDefaultLimit, sharpEdgeAlertsEnabled, staleBookAlertsEnabled, reverseAlertsEnabled, circaChannelId, circaDailyTime, circaTimeZone, circaRetryMinutes, circaMovementFrequencyMinutes, }, }; }