dubs-server 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +280 -0
- package/CLAUDE.md +46 -0
- package/CONNECT4_PRODUCTION_DEPLOY.md +155 -0
- package/CURRENT_SESSION.md +171 -0
- package/CURRENT_SESSION_DRAW.md +516 -0
- package/MARCH_MADNESS_SURVIVOR.md +254 -0
- package/PANDA.md +166 -0
- package/Procfile +4 -0
- package/README.md +476 -0
- package/controllers/livescoresController.js +376 -0
- package/controllers/pickemController.js +554 -0
- package/controllers/survivorAdminController.js +887 -0
- package/controllers/survivorController.js +623 -0
- package/cron/oracleMonitor.js +77 -0
- package/cron/pickemOracleMonitor.js +73 -0
- package/data/jackpot-history.json +952 -0
- package/data/ncaaTeams.js +406 -0
- package/documentation/API_SECURITY_GUIDE.md +327 -0
- package/documentation/ARCADE_API.md +593 -0
- package/documentation/ARCADE_IMPLEMENTATION_SUMMARY.md +399 -0
- package/documentation/ARCADE_QUICKSTART.md +242 -0
- package/documentation/AUTOMATIC_MODE_ORACLE.md +321 -0
- package/documentation/BUG_FIX_COHORT_DATE_DISPLAY.md +171 -0
- package/documentation/CLAIM_MIGRATION_INSTRUCTIONS.md +52 -0
- package/documentation/CLAIM_STATUS_FIX.md +67 -0
- package/documentation/CLI_TOOL_GUIDE.md +372 -0
- package/documentation/COHORT_RETENTION_ANALYSIS.md +295 -0
- package/documentation/COHORT_RETENTION_IMPLEMENTATION_COMPLETE.md +461 -0
- package/documentation/COHORT_RETENTION_SUMMARY.md +204 -0
- package/documentation/COMPLETE_PROJECT_SUMMARY.md +490 -0
- package/documentation/DATABASE_QUERIES.md +269 -0
- package/documentation/DATABASE_RETENTION_POLICY.md +390 -0
- package/documentation/DATABASE_SETUP_GUIDE.md +361 -0
- package/documentation/DATABASE_SETUP_SUMMARY.md +247 -0
- package/documentation/DEMO_API_CURL_COMMANDS.md +656 -0
- package/documentation/DEPLOYMENT_SUMMARY.txt +100 -0
- package/documentation/DUPLICATE_NOTIFICATIONS_FIXED.md +201 -0
- package/documentation/EXCHANGE_RATES_INTEGRATION.md +371 -0
- package/documentation/FINAL_API_PROTECTION_TABLE.md +175 -0
- package/documentation/GAME_START_NOTIFICATIONS_DEPLOYMENT.md +256 -0
- package/documentation/GAME_START_NOTIFICATIONS_INTEGRATION.md +275 -0
- package/documentation/HEROKU_DEPLOYMENT.md +134 -0
- package/documentation/HEROKU_SCHEDULER_SETUP.md +271 -0
- package/documentation/JACKPOT_API.md +521 -0
- package/documentation/JACKPOT_DEPLOYMENT_GUIDE.md +362 -0
- package/documentation/JWT_IMPLEMENTATION_SUMMARY.md +373 -0
- package/documentation/JWT_QUICK_SETUP.md +268 -0
- package/documentation/JWT_TESTING_GUIDE.md +404 -0
- package/documentation/KEEPER_RECOVERY_GUIDE.md +381 -0
- package/documentation/KEEPER_SETUP.md +206 -0
- package/documentation/KEEPER_STATE_MACHINE.md +423 -0
- package/documentation/LATEST_PRODUCTION_SETUP.md +387 -0
- package/documentation/LOCAL_VOTING_TEST.md +279 -0
- package/documentation/ORACLE_FIXES_SUMMARY.md +188 -0
- package/documentation/ORACLE_POSTGRESQL_UPDATE.md +202 -0
- package/documentation/PAYMENT_DEPLOYMENT.md +209 -0
- package/documentation/PNL_TRACKING_SETUP.md +189 -0
- package/documentation/PREVENTING_LOCKUP_ERRORS.md +472 -0
- package/documentation/PRODUCTION_READY_SUMMARY.md +227 -0
- package/documentation/PUBLIC_VS_PRIVATE_ENDPOINTS.md +278 -0
- package/documentation/QUICK_AUTH_SETUP.md +99 -0
- package/documentation/QUICK_DEPLOY.md +224 -0
- package/documentation/QUICK_FIX.md +114 -0
- package/documentation/QUICK_START.md +152 -0
- package/documentation/REFEREE_MODE_GUIDE.md +392 -0
- package/documentation/RETENTION_CORE_ACTION_UPDATE.md +313 -0
- package/documentation/RETENTION_UPDATE_SUMMARY.md +108 -0
- package/documentation/RUN_MIGRATION_NOW.md +39 -0
- package/documentation/SCRIPTS_UPDATE_SUMMARY.md +251 -0
- package/documentation/SETUP_GUIDE.md +184 -0
- package/documentation/STATE_MACHINE_IMPLEMENTATION.md +250 -0
- package/documentation/TELEGRAM_NOTIFICATIONS_DIAGNOSIS.md +361 -0
- package/documentation/UNIFIED_ARCHITECTURE.md +231 -0
- package/documentation/VOTING_DEPLOYMENT_SUMMARY.md +392 -0
- package/documentation/WEBSOCKET_ARCHITECTURE.md +881 -0
- package/documentation/WHAT_WE_BUILT_TODAY.md +369 -0
- package/documentation/latest/LATEST_PRODUCTION_SETUP.md +865 -0
- package/ecosystem.config.js +65 -0
- package/env.template +125 -0
- package/middleware/apiKeyAuth.js +136 -0
- package/middleware/authenticate.js +214 -0
- package/middleware/developerUserAuth.js +76 -0
- package/middleware/socketAuth.js +69 -0
- package/package.json +49 -0
- package/postman/Dubs-API-v1-With-Voting.postman_collection.json +555 -0
- package/postman/Dubs-API-v1.postman_collection.json +205 -0
- package/postman/Dubs_Developer_API.postman_collection.json +662 -0
- package/postman/QUICKSTART.md +118 -0
- package/postman/QUICK_REFERENCE.md +246 -0
- package/postman/README.md +71 -0
- package/postman/VOTING_API_GUIDE.md +426 -0
- package/refactor/Animations.md +148 -0
- package/refactor/Chat.md +252 -0
- package/routes/actionsRoutes.js +699 -0
- package/routes/adminRoutes.js +370 -0
- package/routes/analyticsRoutes.js +1262 -0
- package/routes/arcadeRoutes.js +557 -0
- package/routes/authRoutes.js +2310 -0
- package/routes/avatarRoutes.js +85 -0
- package/routes/botRoutes.js +211 -0
- package/routes/chatRoutes.js +377 -0
- package/routes/cryptoPriceRoutes.js +105 -0
- package/routes/developerRoutes.js +4201 -0
- package/routes/deviceRoutes.js +214 -0
- package/routes/dmRoutes.js +167 -0
- package/routes/esportsRoutes.js +806 -0
- package/routes/exchangeRateRoutes.js +233 -0
- package/routes/gamesRoutes.js +3028 -0
- package/routes/jackpotRoutes.js +754 -0
- package/routes/keeperMonitoringRoutes.js +156 -0
- package/routes/keeperWebhookRoutes.js +466 -0
- package/routes/livescoresRoutes.js +31 -0
- package/routes/pickemAdminRoutes.js +199 -0
- package/routes/pickemRoutes.js +231 -0
- package/routes/playerStatsRoutes.js +147 -0
- package/routes/portfolioRoutes.js +217 -0
- package/routes/promoRoutes.js +418 -0
- package/routes/referralEarningsRoutes.js +392 -0
- package/routes/socialRoutes.js +459 -0
- package/routes/sportsRoutes.js +1271 -0
- package/routes/survivorAdminRoutes.js +345 -0
- package/routes/survivorRoutes.js +756 -0
- package/routes/uploadRoutes.js +256 -0
- package/routes/userProfileRoutes.js +244 -0
- package/routes/whatsNewRoutes.js +331 -0
- package/scripts/.claude/settings.local.json +15 -0
- package/scripts/README.md +170 -0
- package/scripts/RESTART_EVERYTHING.sh +104 -0
- package/scripts/add-claim-columns.sql +48 -0
- package/scripts/add-crypto-prices-cache.sql +27 -0
- package/scripts/add-exchange-rates-cache.sql +40 -0
- package/scripts/add-game-invite-column.sql +23 -0
- package/scripts/add-game-invite-notification.sql +33 -0
- package/scripts/add-game-invite-telegram-pref.sql +16 -0
- package/scripts/add-game-joined-notification.sql +16 -0
- package/scripts/add-game-joined-pref.js +40 -0
- package/scripts/add-game-joined-preference.sql +6 -0
- package/scripts/add-game-start-notifications.sql +41 -0
- package/scripts/add-notification-flags-to-games.sql +55 -0
- package/scripts/add-pending-game-dismissals.sql +19 -0
- package/scripts/add-preferred-currency.sql +34 -0
- package/scripts/add-winner-columns.js +61 -0
- package/scripts/add_mention_system.sql +53 -0
- package/scripts/add_payment_system.sql +96 -0
- package/scripts/add_sports_event_id_column.sql +22 -0
- package/scripts/analyze-cohort-data-heroku.js +276 -0
- package/scripts/analyze-cohort-data.js +295 -0
- package/scripts/analyze-prod-cohorts.sh +10 -0
- package/scripts/backfill-matchup-images.js +245 -0
- package/scripts/backfill-missing-signatures.js +175 -0
- package/scripts/backfill-referral-earnings.js +202 -0
- package/scripts/check-chat-schema.js +130 -0
- package/scripts/check-db.sh +14 -0
- package/scripts/check_oracle_in_game.js +54 -0
- package/scripts/cleanup-database.js +193 -0
- package/scripts/clear-notification-cache.js +85 -0
- package/scripts/convert-mnemonic.js +50 -0
- package/scripts/create-users-table.sql +44 -0
- package/scripts/debug-cohort-counts.js +248 -0
- package/scripts/debug-winner-calc.js +84 -0
- package/scripts/deploy-payment-system.sh +118 -0
- package/scripts/deploy-to-heroku.sh +63 -0
- package/scripts/diagnose-locked-round.js +143 -0
- package/scripts/dubs-cli.js +720 -0
- package/scripts/dump-account.js +65 -0
- package/scripts/find-vrf-offset.js +48 -0
- package/scripts/fix-chat-notifications-constraint.sql +122 -0
- package/scripts/fix-claim-columns.js +124 -0
- package/scripts/fix-constraint-now.js +44 -0
- package/scripts/fix-lock-timestamps.js +96 -0
- package/scripts/fix-locked-round.sh +126 -0
- package/scripts/fix-missing-badges.sql +91 -0
- package/scripts/fix-payment-notifications.sql +41 -0
- package/scripts/force-new-round.js +55 -0
- package/scripts/force-resolve-and-claim.js +278 -0
- package/scripts/important/README.md +115 -0
- package/scripts/important/authority-force-lock.js +197 -0
- package/scripts/important/authority-resolve-game.js +267 -0
- package/scripts/important/check-game-status.js +373 -0
- package/scripts/important/list-pending-games-by-version.js +270 -0
- package/scripts/important/reconcile-v1-v2-payouts.js +270 -0
- package/scripts/initialize-jackpot.js +111 -0
- package/scripts/jackpot/.claude/settings.local.json +10 -0
- package/scripts/jackpot/force-reset.js +84 -0
- package/scripts/jackpot/initialize-mainnet.js +100 -0
- package/scripts/jackpot/keeper.js +742 -0
- package/scripts/jackpot/status.js +107 -0
- package/scripts/jackpot/update-round-duration.js +143 -0
- package/scripts/keeper-bot.js +112 -0
- package/scripts/list-pending-games.js +131 -0
- package/scripts/migrate-chat-v2.js +127 -0
- package/scripts/migrate-chat-winners.js +84 -0
- package/scripts/migrate-chat.sh +17 -0
- package/scripts/migrate-game-invite.js +83 -0
- package/scripts/migrate-heroku-game-notifications.sh +159 -0
- package/scripts/migrations/001_analytics_tables.sql +422 -0
- package/scripts/migrations/002_add_matchup_image_url.sql +14 -0
- package/scripts/migrations/003_referral_earnings.sql +208 -0
- package/scripts/migrations/004_add_whats_new_notification_type.sql +62 -0
- package/scripts/migrations/005_add_connect4_your_turn_notification.sql +61 -0
- package/scripts/migrations/005_push_notifications.sql +55 -0
- package/scripts/migrations/006_add_draw_team_players.sql +28 -0
- package/scripts/migrations/006_add_game_cancelled_notification.sql +62 -0
- package/scripts/migrations/007_add_gif_url.sql +8 -0
- package/scripts/migrations/008_add_connect4_columns.sql +139 -0
- package/scripts/migrations/008_add_pool_tracking.sql +22 -0
- package/scripts/migrations/009_create_survivor_pool_tables.sql +174 -0
- package/scripts/migrations/010_add_survivor_pool_outcome.sql +28 -0
- package/scripts/migrations/011_create_developer_tables.sql +67 -0
- package/scripts/migrations/011_fix_keeper_tables.sql +85 -0
- package/scripts/migrations/012_create_developer_webhooks.sql +31 -0
- package/scripts/migrations/013_add_network_mode.sql +18 -0
- package/scripts/migrations/014_create_developer_app_users.sql +19 -0
- package/scripts/migrations/015_add_ui_config.sql +4 -0
- package/scripts/migrations/016_add_resolution_secret.sql +4 -0
- package/scripts/migrations/017_add_external_game_id.sql +3 -0
- package/scripts/migrations/018_create_pickem_tables.sql +115 -0
- package/scripts/migrations/019_expo_push_tokens.sql +19 -0
- package/scripts/migrations/create_whats_new_tables.sql +88 -0
- package/scripts/migrations/drop_live_games_tables.sql +34 -0
- package/scripts/open-jackpot-round.js +85 -0
- package/scripts/purge-all-data.sh +329 -0
- package/scripts/purge-all-data.sql +142 -0
- package/scripts/purge-heroku-data.sh +149 -0
- package/scripts/purge-heroku-data.sql +62 -0
- package/scripts/rebuild-heroku-database.sh +113 -0
- package/scripts/recover-funds.js +357 -0
- package/scripts/regenerate-epl-images.js +278 -0
- package/scripts/resize-s3-matchup-images.js +374 -0
- package/scripts/resolve-direct.js +88 -0
- package/scripts/resolve-mock-game.js +124 -0
- package/scripts/resolve-pickem-game.js +55 -0
- package/scripts/resolve-round-manual.js +83 -0
- package/scripts/resolve-stuck-game.js +382 -0
- package/scripts/resolve-stuck-round.js +42 -0
- package/scripts/run-connect4-migration.sh +16 -0
- package/scripts/run-mention-migration.sh +32 -0
- package/scripts/run-payment-migration.sh +51 -0
- package/scripts/run-preferred-currency-migration.sh +31 -0
- package/scripts/run-referral-earnings-migration.sh +32 -0
- package/scripts/run-survivor-outcome-migration.sh +16 -0
- package/scripts/seed-test-users.js +346 -0
- package/scripts/setup-auth-tables.js +78 -0
- package/scripts/setup-complete-database.sql +992 -0
- package/scripts/setup-database-fresh.sh +359 -0
- package/scripts/setup-heroku-keeper.sh +48 -0
- package/scripts/setup-keeper-database.js +83 -0
- package/scripts/setup-keeper-state-db.sql +110 -0
- package/scripts/setup-oracle.sh +39 -0
- package/scripts/setup-pnl-tracking.js +111 -0
- package/scripts/start-devnet.sh +14 -0
- package/scripts/test-arcade-devnet.sh +160 -0
- package/scripts/test-arcade-match.sh +109 -0
- package/scripts/test-automatic-mode.sh +239 -0
- package/scripts/test-connect4-cancel-claim.js +370 -0
- package/scripts/test-connect4-e2e.js +369 -0
- package/scripts/test-connect4-resolve.js +369 -0
- package/scripts/test-game-state-endpoint.js +136 -0
- package/scripts/test-invite-notification.js +86 -0
- package/scripts/test-jackpot-api.sh +71 -0
- package/scripts/test-poll-confirmation.js +267 -0
- package/scripts/test-resolve-game.js +271 -0
- package/scripts/test-resolve-signature.js +223 -0
- package/scripts/test-signature-preservation.js +124 -0
- package/scripts/test-state-machine.js +291 -0
- package/scripts/test-webhook-receiver.js +60 -0
- package/scripts/update-notification-constraint.js +52 -0
- package/scripts/verify-account-layout.js +145 -0
- package/scripts/verify-winner-algorithm.js +278 -0
- package/server.js +5259 -0
- package/services/arcadeMatchService.js +763 -0
- package/services/automaticGameOracle.js +1596 -0
- package/services/chatService.js +1612 -0
- package/services/connect4GameService.js +1049 -0
- package/services/connect4NotificationService.js +374 -0
- package/services/cryptoPriceService.js +223 -0
- package/services/customGameResolver.js +260 -0
- package/services/db.js +79 -0
- package/services/directMessageService.js +389 -0
- package/services/discordNotifications.js +160 -0
- package/services/exchangeRateService.js +289 -0
- package/services/expoPushService.js +314 -0
- package/services/gamesCacheService.js +539 -0
- package/services/jackpotHistory.js +331 -0
- package/services/jackpotService.js +856 -0
- package/services/keeperStateService.js +355 -0
- package/services/matchupImageService.js +591 -0
- package/services/notificationCacheService.js +407 -0
- package/services/pickemOracle.js +440 -0
- package/services/playerStatsService.js +389 -0
- package/services/portfolioService.js +555 -0
- package/services/promoService.js +757 -0
- package/services/promoTreasuryService.js +239 -0
- package/services/pushNotifications.js +353 -0
- package/services/redisService.js +422 -0
- package/services/referralEarningsService.js +728 -0
- package/services/s3Service.js +396 -0
- package/services/socialService.js +1202 -0
- package/services/survivorOracle.js +469 -0
- package/services/survivorSimulator.js +475 -0
- package/services/telegramNotifications.js +461 -0
- package/services/userProfileStatsService.js +1185 -0
- package/services/whatsNewService.js +388 -0
- package/utils/urlHelper.js +95 -0
|
@@ -0,0 +1,461 @@
|
|
|
1
|
+
// 🔔 Telegram Notification Service for Oracle
|
|
2
|
+
// Sends notifications to Telegram users when games are resolved
|
|
3
|
+
|
|
4
|
+
const axios = require('axios');
|
|
5
|
+
const urlHelper = require('../utils/urlHelper');
|
|
6
|
+
|
|
7
|
+
const BOT_TOKEN = process.env.TELEGRAM_BOT_TOKEN;
|
|
8
|
+
const TELEGRAM_API = `https://api.telegram.org/bot${BOT_TOKEN}`;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Escape special Markdown characters in user-generated text
|
|
12
|
+
* Prevents Telegram API parse errors from user messages containing *, _, `, [, etc.
|
|
13
|
+
*/
|
|
14
|
+
function escapeMarkdown(text) {
|
|
15
|
+
if (!text) return '';
|
|
16
|
+
// Escape Markdown special characters: _ * ` [
|
|
17
|
+
return text
|
|
18
|
+
.replace(/\\/g, '\\\\') // Escape backslashes first
|
|
19
|
+
.replace(/_/g, '\\_')
|
|
20
|
+
.replace(/\*/g, '\\*')
|
|
21
|
+
.replace(/`/g, '\\`')
|
|
22
|
+
.replace(/\[/g, '\\[');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Send a Telegram message
|
|
27
|
+
*/
|
|
28
|
+
async function sendTelegramMessage(chatId, message, options = {}) {
|
|
29
|
+
if (!BOT_TOKEN) {
|
|
30
|
+
console.log('[TelegramNotif] ⚠️ TELEGRAM_BOT_TOKEN not set - skipping notification');
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
const payload = {
|
|
36
|
+
chat_id: chatId,
|
|
37
|
+
text: message,
|
|
38
|
+
parse_mode: 'Markdown',
|
|
39
|
+
disable_web_page_preview: true,
|
|
40
|
+
...options
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
console.log(`[TelegramNotif] 🚀 Calling Telegram API for chat_id: ${chatId}`);
|
|
44
|
+
const response = await axios.post(`${TELEGRAM_API}/sendMessage`, payload);
|
|
45
|
+
|
|
46
|
+
if (response.data.ok) {
|
|
47
|
+
console.log(`[TelegramNotif] ✅ Telegram API returned OK for chat_id: ${chatId}`);
|
|
48
|
+
} else {
|
|
49
|
+
console.log(`[TelegramNotif] ⚠️ Telegram API returned not OK:`, response.data);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return response.data.ok;
|
|
53
|
+
} catch (error) {
|
|
54
|
+
// Log more details about the error
|
|
55
|
+
if (error.response) {
|
|
56
|
+
console.error(`[TelegramNotif] ❌ Telegram API error (${error.response.status}):`, error.response.data?.description || error.message);
|
|
57
|
+
} else {
|
|
58
|
+
console.error('[TelegramNotif] ❌ Failed to send Telegram message:', error.message);
|
|
59
|
+
}
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Notify user about game result (DEPRECATED - use notifyGroupGameResult instead)
|
|
66
|
+
*/
|
|
67
|
+
async function notifyGameResult(telegramUserId, gameData, result) {
|
|
68
|
+
const { gameId, sportsEvent, participants } = gameData;
|
|
69
|
+
const { winner, homeScore, awayScore } = result;
|
|
70
|
+
|
|
71
|
+
// Find this user's participation
|
|
72
|
+
const userParticipant = participants?.find(p => p.telegramUserId === telegramUserId);
|
|
73
|
+
|
|
74
|
+
if (!userParticipant) {
|
|
75
|
+
return false; // User not in this game
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const userTeam = userParticipant.teamChoice;
|
|
79
|
+
const didWin = userTeam === winner;
|
|
80
|
+
|
|
81
|
+
const emoji = didWin ? '🎉' : '😔';
|
|
82
|
+
const status = didWin ? '*YOU WON!*' : '*Game Over*';
|
|
83
|
+
const homeTeam = sportsEvent?.strHomeTeam || 'Home';
|
|
84
|
+
const awayTeam = sportsEvent?.strAwayTeam || 'Away';
|
|
85
|
+
|
|
86
|
+
const claimCommand = didWin ? `/claim ${gameId}` : '';
|
|
87
|
+
|
|
88
|
+
const message = `
|
|
89
|
+
${emoji} *Game Result*
|
|
90
|
+
|
|
91
|
+
${status}
|
|
92
|
+
|
|
93
|
+
*${awayTeam}* ${awayScore} - ${homeScore} *${homeTeam}*
|
|
94
|
+
|
|
95
|
+
${didWin ? '🏆 You picked the winning team!' : '💪 Better luck next time!'}
|
|
96
|
+
|
|
97
|
+
Your team: ${userTeam === 'home' ? homeTeam : awayTeam}
|
|
98
|
+
Winner: ${winner === 'home' ? homeTeam : awayTeam}
|
|
99
|
+
|
|
100
|
+
${didWin ? '💰 *Claim your winnings:*\n\`' + claimCommand + '\`\n\n_Tap to copy, then send as a message!_' : ''}
|
|
101
|
+
`.trim();
|
|
102
|
+
|
|
103
|
+
return await sendTelegramMessage(telegramUserId, message);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Notify group chat about game result with winners and losers
|
|
108
|
+
*/
|
|
109
|
+
async function notifyGroupGameResult(gameData, result) {
|
|
110
|
+
const { gameId, sportsEvent, participants, telegramChatId } = gameData;
|
|
111
|
+
const { winner, homeScore, awayScore } = result;
|
|
112
|
+
|
|
113
|
+
if (!telegramChatId) {
|
|
114
|
+
console.log('⚠️ No telegramChatId found - cannot send group notification');
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const homeTeam = sportsEvent?.strHomeTeam || 'Home';
|
|
119
|
+
const awayTeam = sportsEvent?.strAwayTeam || 'Away';
|
|
120
|
+
|
|
121
|
+
// Separate winners and losers
|
|
122
|
+
const winners = [];
|
|
123
|
+
const losers = [];
|
|
124
|
+
|
|
125
|
+
participants?.forEach(participant => {
|
|
126
|
+
const didWin = participant.teamChoice === winner;
|
|
127
|
+
const username = participant.username || 'User';
|
|
128
|
+
|
|
129
|
+
if (didWin) {
|
|
130
|
+
winners.push(username);
|
|
131
|
+
} else {
|
|
132
|
+
losers.push(username);
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
// Build the message
|
|
137
|
+
let message = `
|
|
138
|
+
🏁 *GAME FINISHED!*
|
|
139
|
+
|
|
140
|
+
*${awayTeam}* ${awayScore} - ${homeScore} *${homeTeam}*
|
|
141
|
+
|
|
142
|
+
🏆 *Winner:* ${winner === 'home' ? homeTeam : awayTeam}
|
|
143
|
+
`;
|
|
144
|
+
|
|
145
|
+
if (winners.length > 0) {
|
|
146
|
+
message += `\n\n🎉 *Winners:*\n`;
|
|
147
|
+
winners.forEach(username => {
|
|
148
|
+
message += `• ${username}\n`;
|
|
149
|
+
});
|
|
150
|
+
message += `\n💰 *Claim your winnings:*\n\`/claim ${gameId}\`\n\n_Tap to copy, then send as a message!_`;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (losers.length > 0) {
|
|
154
|
+
message += `\n\n😔 *Better luck next time:*\n`;
|
|
155
|
+
losers.forEach(username => {
|
|
156
|
+
message += `• ${username}\n`;
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
message = message.trim();
|
|
161
|
+
|
|
162
|
+
return await sendTelegramMessage(telegramChatId, message);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Get Telegram user IDs from wallet addresses (requires mapping)
|
|
167
|
+
* For now, you'll need to store wallet -> telegramUserId mapping
|
|
168
|
+
*/
|
|
169
|
+
async function getParticipantTelegramIds(gameData) {
|
|
170
|
+
// TODO: Implement wallet address -> Telegram ID mapping
|
|
171
|
+
// This could be done via:
|
|
172
|
+
// 1. Firebase lookup
|
|
173
|
+
// 2. Bot's userManager (needs to be accessible)
|
|
174
|
+
// 3. Separate database
|
|
175
|
+
|
|
176
|
+
// For now, return empty array
|
|
177
|
+
// You'll need to add this mapping when users create games
|
|
178
|
+
return [];
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Notify group chat that game is starting soon (10 min warning)
|
|
183
|
+
*/
|
|
184
|
+
async function notifyGameStartingSoon(gameData, minutesUntilStart) {
|
|
185
|
+
const { gameId, sportsEvent, participants, telegramChatId } = gameData;
|
|
186
|
+
|
|
187
|
+
if (!telegramChatId) {
|
|
188
|
+
console.log('⚠️ No telegramChatId found - cannot send group notification');
|
|
189
|
+
return false;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const homeTeam = sportsEvent?.strHomeTeam || 'Home';
|
|
193
|
+
const awayTeam = sportsEvent?.strAwayTeam || 'Away';
|
|
194
|
+
|
|
195
|
+
// Build the message
|
|
196
|
+
let message = `
|
|
197
|
+
⏰ *GAME STARTING SOON!*
|
|
198
|
+
|
|
199
|
+
*${awayTeam}* @ *${homeTeam}*
|
|
200
|
+
|
|
201
|
+
🔒 Betting closes in *${minutesUntilStart} minute${minutesUntilStart !== 1 ? 's' : ''}*!
|
|
202
|
+
|
|
203
|
+
`;
|
|
204
|
+
|
|
205
|
+
if (participants && participants.length > 0) {
|
|
206
|
+
message += `👥 *Current Bets:* ${participants.length} player${participants.length !== 1 ? 's' : ''}\n`;
|
|
207
|
+
|
|
208
|
+
const homeCount = participants.filter(p => p.teamChoice === 'home').length;
|
|
209
|
+
const awayCount = participants.filter(p => p.teamChoice === 'away').length;
|
|
210
|
+
|
|
211
|
+
if (homeCount > 0 || awayCount > 0) {
|
|
212
|
+
message += ` • ${homeTeam}: ${homeCount}\n`;
|
|
213
|
+
message += ` • ${awayTeam}: ${awayCount}\n`;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
message += `\n⚡ Last chance to join: \`/join ${gameId}\``;
|
|
218
|
+
message = message.trim();
|
|
219
|
+
|
|
220
|
+
return await sendTelegramMessage(telegramChatId, message);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Notify group chat that game is starting NOW (betting closed)
|
|
225
|
+
*/
|
|
226
|
+
async function notifyGameStartingNow(gameData) {
|
|
227
|
+
const { gameId, sportsEvent, participants, telegramChatId } = gameData;
|
|
228
|
+
|
|
229
|
+
if (!telegramChatId) {
|
|
230
|
+
console.log('⚠️ No telegramChatId found - cannot send group notification');
|
|
231
|
+
return false;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const homeTeam = sportsEvent?.strHomeTeam || 'Home';
|
|
235
|
+
const awayTeam = sportsEvent?.strAwayTeam || 'Away';
|
|
236
|
+
|
|
237
|
+
// Build the message
|
|
238
|
+
let message = `
|
|
239
|
+
🚨 *GAME STARTING NOW!*
|
|
240
|
+
|
|
241
|
+
*${awayTeam}* @ *${homeTeam}*
|
|
242
|
+
|
|
243
|
+
🔒 *Betting is now CLOSED*
|
|
244
|
+
|
|
245
|
+
`;
|
|
246
|
+
|
|
247
|
+
if (participants && participants.length > 0) {
|
|
248
|
+
const homeCount = participants.filter(p => p.teamChoice === 'home').length;
|
|
249
|
+
const awayCount = participants.filter(p => p.teamChoice === 'away').length;
|
|
250
|
+
|
|
251
|
+
message += `👥 *Final Bets:* ${participants.length} player${participants.length !== 1 ? 's' : ''}\n`;
|
|
252
|
+
message += ` 🏠 ${homeTeam}: ${homeCount}\n`;
|
|
253
|
+
message += ` ✈️ ${awayTeam}: ${awayCount}\n\n`;
|
|
254
|
+
|
|
255
|
+
message += `🎮 *Game is LIVE!* Good luck everyone! 🍀\n`;
|
|
256
|
+
message += `\n📊 Results will be posted automatically when the game finishes.`;
|
|
257
|
+
} else {
|
|
258
|
+
message += `_No bets placed on this game._`;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
message = message.trim();
|
|
262
|
+
|
|
263
|
+
return await sendTelegramMessage(telegramChatId, message);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Forward chat notification to Telegram if user has connected their account
|
|
268
|
+
* @param {object} pool - Database pool
|
|
269
|
+
* @param {string} userId - User ID to send notification to
|
|
270
|
+
* @param {string} notificationType - Type of notification (game_invite, game_joined, etc.)
|
|
271
|
+
* @param {string} senderUsername - Username of the sender
|
|
272
|
+
* @param {string} message - Notification message text
|
|
273
|
+
* @param {object} metadata - Optional metadata (e.g., { gameId: 'sport-xxx' } for game invites)
|
|
274
|
+
*/
|
|
275
|
+
async function forwardChatNotification(pool, userId, notificationType, senderUsername, message = '', metadata = {}) {
|
|
276
|
+
try {
|
|
277
|
+
console.log(`[TelegramNotif] 📤 Attempting to forward ${notificationType} notification to user ${userId} from @${senderUsername}`);
|
|
278
|
+
|
|
279
|
+
// Get user's telegram_user_id and preferences
|
|
280
|
+
const result = await pool.query(
|
|
281
|
+
`SELECT u.telegram_user_id, u.username,
|
|
282
|
+
COALESCE(p.notify_reply, true) as notify_reply,
|
|
283
|
+
COALESCE(p.notify_reaction, true) as notify_reaction,
|
|
284
|
+
COALESCE(p.notify_friend_request, true) as notify_friend_request,
|
|
285
|
+
COALESCE(p.notify_friend_request_accepted, true) as notify_friend_request_accepted,
|
|
286
|
+
COALESCE(p.notify_friend_request_declined, true) as notify_friend_request_declined,
|
|
287
|
+
COALESCE(p.notify_referral, true) as notify_referral,
|
|
288
|
+
COALESCE(p.notify_mention, true) as notify_mention,
|
|
289
|
+
COALESCE(p.notify_friend_message, true) as notify_friend_message,
|
|
290
|
+
COALESCE(p.notify_game_joined, true) as notify_game_joined,
|
|
291
|
+
COALESCE(p.notify_game_invite, true) as notify_game_invite
|
|
292
|
+
FROM users u
|
|
293
|
+
LEFT JOIN telegram_notification_preferences p ON u.id = p.user_id
|
|
294
|
+
WHERE u.id = $1 AND u.telegram_user_id IS NOT NULL`,
|
|
295
|
+
[userId]
|
|
296
|
+
);
|
|
297
|
+
|
|
298
|
+
if (result.rows.length === 0) {
|
|
299
|
+
console.log(`[TelegramNotif] ⏭️ User ${userId} doesn't have Telegram connected - skipping`);
|
|
300
|
+
return false; // User doesn't have Telegram connected
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const prefs = result.rows[0];
|
|
304
|
+
const telegramUserId = prefs.telegram_user_id;
|
|
305
|
+
const recipientUsername = prefs.username;
|
|
306
|
+
|
|
307
|
+
console.log(`[TelegramNotif] 👤 Found user @${recipientUsername} with Telegram ID: ${telegramUserId}`);
|
|
308
|
+
|
|
309
|
+
// Check if user wants this notification type
|
|
310
|
+
const prefMap = {
|
|
311
|
+
'reply': prefs.notify_reply,
|
|
312
|
+
'reaction': prefs.notify_reaction,
|
|
313
|
+
'friend_request': prefs.notify_friend_request,
|
|
314
|
+
'friend_request_accepted': prefs.notify_friend_request_accepted,
|
|
315
|
+
'friend_request_declined': prefs.notify_friend_request_declined,
|
|
316
|
+
'referral': prefs.notify_referral,
|
|
317
|
+
'mention': prefs.notify_mention,
|
|
318
|
+
'friend_message': prefs.notify_friend_message,
|
|
319
|
+
'game_joined': prefs.notify_game_joined,
|
|
320
|
+
'game_invite': prefs.notify_game_invite,
|
|
321
|
+
'dm_message': prefs.notify_friend_message, // Use friend_message pref for DMs
|
|
322
|
+
'connect4_your_turn': prefs.notify_game_invite, // Use game_invite pref for Connect4 turn notifications
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
if (prefMap[notificationType] === false) {
|
|
326
|
+
console.log(`[TelegramNotif] 🔇 User @${recipientUsername} has disabled ${notificationType} notifications - skipping`);
|
|
327
|
+
return false; // User disabled this notification type
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Format notification message based on type
|
|
331
|
+
// Escape user-generated content to prevent Markdown parse errors
|
|
332
|
+
const safeSenderUsername = escapeMarkdown(senderUsername);
|
|
333
|
+
const safeMessage = escapeMarkdown(message);
|
|
334
|
+
|
|
335
|
+
let notificationText = '🔔 *Notification*\n\n';
|
|
336
|
+
|
|
337
|
+
switch (notificationType) {
|
|
338
|
+
case 'reply':
|
|
339
|
+
notificationText += `${safeSenderUsername} replied to your message`;
|
|
340
|
+
if (safeMessage) notificationText += `\n\n"${safeMessage}"`;
|
|
341
|
+
break;
|
|
342
|
+
case 'reaction':
|
|
343
|
+
notificationText += `${safeSenderUsername} reacted ${safeMessage} to your message`;
|
|
344
|
+
break;
|
|
345
|
+
case 'friend_request':
|
|
346
|
+
notificationText += `${safeSenderUsername} sent you a friend request`;
|
|
347
|
+
break;
|
|
348
|
+
case 'friend_request_accepted':
|
|
349
|
+
notificationText += `${safeSenderUsername} accepted your friend request!`;
|
|
350
|
+
break;
|
|
351
|
+
case 'friend_request_declined':
|
|
352
|
+
notificationText += `${safeSenderUsername} declined your friend request`;
|
|
353
|
+
break;
|
|
354
|
+
case 'referral':
|
|
355
|
+
notificationText += `🎉 ${safeSenderUsername} joined using your referral code!`;
|
|
356
|
+
break;
|
|
357
|
+
case 'game_joined':
|
|
358
|
+
notificationText += `🏆 ${safeSenderUsername} joined your bet!\n\n${safeMessage}`;
|
|
359
|
+
break;
|
|
360
|
+
case 'game_invite':
|
|
361
|
+
notificationText += `🎮 ${safeSenderUsername} invited you to join their bet!\n\n${safeMessage}`;
|
|
362
|
+
// CTA button will be added below if gameId is provided
|
|
363
|
+
break;
|
|
364
|
+
case 'mention':
|
|
365
|
+
notificationText += `${safeSenderUsername} mentioned you`;
|
|
366
|
+
if (safeMessage) notificationText += `\n\n"${safeMessage}"`;
|
|
367
|
+
break;
|
|
368
|
+
case 'friend_message':
|
|
369
|
+
notificationText += `${safeSenderUsername} sent you a message`;
|
|
370
|
+
if (safeMessage) notificationText += `\n\n"${safeMessage}"`;
|
|
371
|
+
break;
|
|
372
|
+
case 'dm_message':
|
|
373
|
+
notificationText += `💬 ${safeSenderUsername} sent you a direct message`;
|
|
374
|
+
if (safeMessage) notificationText += `\n\n"${safeMessage}"`;
|
|
375
|
+
break;
|
|
376
|
+
case 'game_won':
|
|
377
|
+
notificationText = `🏆 *You Won!*\n\n${safeMessage}`;
|
|
378
|
+
break;
|
|
379
|
+
case 'game_lost':
|
|
380
|
+
notificationText = `🎱 *Game Finished*\n\n${safeMessage}`;
|
|
381
|
+
break;
|
|
382
|
+
case 'game_starting_soon':
|
|
383
|
+
notificationText = `⏰ *Game Starting Soon!*\n\n${safeMessage}`;
|
|
384
|
+
break;
|
|
385
|
+
case 'game_starting_now':
|
|
386
|
+
notificationText = `🚨 *Game Starting NOW!*\n\n${safeMessage}`;
|
|
387
|
+
break;
|
|
388
|
+
case 'whats_new':
|
|
389
|
+
notificationText = `✨ *What's New on Dubs!*\n\n${safeMessage}`;
|
|
390
|
+
break;
|
|
391
|
+
case 'connect4_your_turn':
|
|
392
|
+
notificationText = `🔴🟡 *Your Turn!*\n\n${safeSenderUsername} made a move in 4 the Pot - it's your turn!`;
|
|
393
|
+
break;
|
|
394
|
+
default:
|
|
395
|
+
notificationText += `${safeSenderUsername} - ${notificationType}`;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
console.log(`[TelegramNotif] 📨 Sending ${notificationType} notification to Telegram user ${telegramUserId}...`);
|
|
399
|
+
|
|
400
|
+
// Build message options (CTA buttons for certain notification types)
|
|
401
|
+
const messageOptions = {};
|
|
402
|
+
|
|
403
|
+
// Add CTA button for game-related notifications
|
|
404
|
+
if (metadata.gameId) {
|
|
405
|
+
const gameUrl = urlHelper.getGameShareUrl(metadata.gameId);
|
|
406
|
+
console.log(`[TelegramNotif] 🔗 Adding CTA button with URL: ${gameUrl}`);
|
|
407
|
+
|
|
408
|
+
// Different button text based on notification type
|
|
409
|
+
let buttonText = '🎮 View Game';
|
|
410
|
+
if (notificationType === 'game_invite') {
|
|
411
|
+
buttonText = '🎮 Join Game';
|
|
412
|
+
} else if (notificationType === 'game_joined') {
|
|
413
|
+
buttonText = '🎮 View Game';
|
|
414
|
+
} else if (notificationType === 'connect4_your_turn') {
|
|
415
|
+
buttonText = '🔴🟡 Make Your Move';
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
messageOptions.reply_markup = {
|
|
419
|
+
inline_keyboard: [[
|
|
420
|
+
{ text: buttonText, url: gameUrl }
|
|
421
|
+
]]
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// Add CTA button for What's New notifications
|
|
426
|
+
if (notificationType === 'whats_new' && metadata.postId) {
|
|
427
|
+
const whatsNewUrl = urlHelper.getWhatsNewUrl(metadata.postId);
|
|
428
|
+
console.log(`[TelegramNotif] 🔗 Adding What's New CTA button with URL: ${whatsNewUrl}`);
|
|
429
|
+
|
|
430
|
+
messageOptions.reply_markup = {
|
|
431
|
+
inline_keyboard: [[
|
|
432
|
+
{ text: '✨ See What\'s New', url: whatsNewUrl }
|
|
433
|
+
]]
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
const sent = await sendTelegramMessage(telegramUserId, notificationText, messageOptions);
|
|
438
|
+
|
|
439
|
+
if (sent) {
|
|
440
|
+
console.log(`[TelegramNotif] ✅ Successfully sent ${notificationType} notification to @${recipientUsername}`);
|
|
441
|
+
} else {
|
|
442
|
+
console.log(`[TelegramNotif] ❌ Failed to send ${notificationType} notification to @${recipientUsername}`);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
return sent;
|
|
446
|
+
} catch (error) {
|
|
447
|
+
console.error(`[TelegramNotif] ❌ Error forwarding ${notificationType} notification to Telegram:`, error.message);
|
|
448
|
+
return false;
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
module.exports = {
|
|
453
|
+
sendTelegramMessage,
|
|
454
|
+
notifyGameResult,
|
|
455
|
+
notifyGroupGameResult,
|
|
456
|
+
notifyGameStartingSoon,
|
|
457
|
+
notifyGameStartingNow,
|
|
458
|
+
getParticipantTelegramIds,
|
|
459
|
+
forwardChatNotification
|
|
460
|
+
};
|
|
461
|
+
|