Fix draw notification spam and winning ticket number
- Group participants by telegramId to send only ONE message per user - First pass finds the winning ticket info before sending any messages - Loser messages now show the actual winning ticket number instead of #0000 - Unique participant count used for group announcements
This commit is contained in:
@@ -136,86 +136,112 @@ class NotificationScheduler {
|
||||
|
||||
// Get participants for the previous cycle
|
||||
const participants = await stateManager.getCycleParticipants(previousCycleId);
|
||||
const hasParticipants = participants.length > 0;
|
||||
|
||||
logger.info('Processing previous cycle completion', {
|
||||
cycleId: previousCycleId,
|
||||
participantCount: participants.length
|
||||
});
|
||||
|
||||
// Get draw result details for winner announcement
|
||||
if (participants.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Group participants by telegramId to avoid spamming users with multiple purchases
|
||||
const userPurchases = new Map<number, string[]>();
|
||||
for (const participant of participants) {
|
||||
const existing = userPurchases.get(participant.telegramId) || [];
|
||||
existing.push(participant.purchaseId);
|
||||
userPurchases.set(participant.telegramId, existing);
|
||||
}
|
||||
|
||||
// First pass: Find the winning ticket info and pot amount
|
||||
let winnerDisplayName = 'Anon';
|
||||
let winnerTicketNumber = '0000';
|
||||
let winnerTelegramId: number | null = null;
|
||||
let potSats = 0;
|
||||
let prizeSats = 0;
|
||||
let payoutStatus = 'processing';
|
||||
|
||||
for (const [telegramId, purchaseIds] of userPurchases) {
|
||||
for (const purchaseId of purchaseIds) {
|
||||
try {
|
||||
const status = await apiClient.getTicketStatus(purchaseId);
|
||||
if (!status) continue;
|
||||
|
||||
potSats = status.cycle.pot_total_sats || 0;
|
||||
|
||||
if (status.result.is_winner) {
|
||||
const user = await stateManager.getUser(telegramId);
|
||||
winnerTelegramId = telegramId;
|
||||
winnerDisplayName = user?.displayName || 'Anon';
|
||||
|
||||
const winningTicket = status.tickets.find(t => t.is_winning_ticket);
|
||||
if (winningTicket) {
|
||||
winnerTicketNumber = winningTicket.serial_number.toString().padStart(4, '0');
|
||||
}
|
||||
|
||||
prizeSats = status.result.payout?.amount_sats || potSats;
|
||||
payoutStatus = status.result.payout?.status || 'processing';
|
||||
break; // Found winner, stop searching
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Error checking purchase status', { purchaseId, error });
|
||||
}
|
||||
}
|
||||
if (winnerTelegramId) break; // Found winner, stop searching
|
||||
}
|
||||
|
||||
// Second pass: Send ONE notification per user
|
||||
const notifiedUsers = new Set<number>();
|
||||
|
||||
for (const [telegramId, purchaseIds] of userPurchases) {
|
||||
if (notifiedUsers.has(telegramId)) continue;
|
||||
notifiedUsers.add(telegramId);
|
||||
|
||||
// Notify each participant about their result
|
||||
for (const participant of participants) {
|
||||
try {
|
||||
const user = await stateManager.getUser(participant.telegramId);
|
||||
if (!user) continue;
|
||||
const user = await stateManager.getUser(telegramId);
|
||||
if (!user || user.notifications?.drawResults === false) continue;
|
||||
|
||||
// Check if they won
|
||||
const status = await apiClient.getTicketStatus(participant.purchaseId);
|
||||
if (!status) continue;
|
||||
|
||||
potSats = status.cycle.pot_total_sats || 0;
|
||||
const isWinner = status.result.is_winner;
|
||||
const isWinner = telegramId === winnerTelegramId;
|
||||
|
||||
if (isWinner) {
|
||||
// Store winner info for group announcement
|
||||
winnerDisplayName = user.displayName || 'Anon';
|
||||
const winningTicket = status.tickets.find(t => t.is_winning_ticket);
|
||||
if (winningTicket) {
|
||||
winnerTicketNumber = winningTicket.serial_number.toString().padStart(4, '0');
|
||||
}
|
||||
|
||||
// Send winner notification if user has drawResults enabled
|
||||
if (user.notifications?.drawResults !== false) {
|
||||
const prizeSats = status.result.payout?.amount_sats || potSats;
|
||||
const payoutStatus = status.result.payout?.status || 'processing';
|
||||
await this.bot.sendMessage(
|
||||
participant.telegramId,
|
||||
messages.notifications.winner(
|
||||
prizeSats.toLocaleString(),
|
||||
winnerTicketNumber,
|
||||
payoutStatus
|
||||
),
|
||||
{ parse_mode: 'Markdown' }
|
||||
);
|
||||
logger.info('Sent winner notification', { telegramId: participant.telegramId });
|
||||
}
|
||||
// Send winner notification
|
||||
await this.bot.sendMessage(
|
||||
telegramId,
|
||||
messages.notifications.winner(
|
||||
prizeSats.toLocaleString(),
|
||||
winnerTicketNumber,
|
||||
payoutStatus
|
||||
),
|
||||
{ parse_mode: 'Markdown' }
|
||||
);
|
||||
logger.info('Sent winner notification', { telegramId });
|
||||
} else {
|
||||
// Send loser notification if user has drawResults enabled
|
||||
if (user.notifications?.drawResults !== false) {
|
||||
await this.bot.sendMessage(
|
||||
participant.telegramId,
|
||||
messages.notifications.loser(
|
||||
winnerTicketNumber,
|
||||
potSats.toLocaleString()
|
||||
),
|
||||
{ parse_mode: 'Markdown' }
|
||||
);
|
||||
logger.debug('Sent draw result to participant', { telegramId: participant.telegramId });
|
||||
}
|
||||
// Send loser notification with actual winning ticket number
|
||||
await this.bot.sendMessage(
|
||||
telegramId,
|
||||
messages.notifications.loser(
|
||||
winnerTicketNumber,
|
||||
potSats.toLocaleString()
|
||||
),
|
||||
{ parse_mode: 'Markdown' }
|
||||
);
|
||||
logger.debug('Sent draw result to participant', { telegramId });
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Failed to notify participant', {
|
||||
telegramId: participant.telegramId,
|
||||
error
|
||||
});
|
||||
logger.error('Failed to notify participant', { telegramId, error });
|
||||
}
|
||||
}
|
||||
|
||||
// Send group announcements (even if no participants - groups might want to know)
|
||||
if (hasParticipants) {
|
||||
await this.sendGroupDrawAnnouncementsImmediate(
|
||||
previousCycleId,
|
||||
winnerDisplayName,
|
||||
winnerTicketNumber,
|
||||
potSats,
|
||||
participants.length
|
||||
);
|
||||
}
|
||||
// Send group announcements
|
||||
const uniqueUserCount = userPurchases.size;
|
||||
await this.sendGroupDrawAnnouncementsImmediate(
|
||||
previousCycleId,
|
||||
winnerDisplayName,
|
||||
winnerTicketNumber,
|
||||
potSats,
|
||||
uniqueUserCount
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -333,7 +359,6 @@ class NotificationScheduler {
|
||||
|
||||
// Get participants for this cycle
|
||||
const participants = await stateManager.getCycleParticipants(cycle.id);
|
||||
const hasParticipants = participants.length > 0;
|
||||
|
||||
logger.info('Processing draw completion', {
|
||||
cycleId: cycle.id,
|
||||
@@ -342,40 +367,70 @@ class NotificationScheduler {
|
||||
});
|
||||
|
||||
// Only proceed if there were participants
|
||||
if (!hasParticipants) {
|
||||
if (participants.length === 0) {
|
||||
logger.info('No participants in cycle, skipping notifications', { cycleId: cycle.id });
|
||||
return;
|
||||
}
|
||||
|
||||
// Get draw result details for winner announcement
|
||||
// Group participants by telegramId to avoid spamming users with multiple purchases
|
||||
const userPurchases = new Map<number, string[]>();
|
||||
for (const participant of participants) {
|
||||
const existing = userPurchases.get(participant.telegramId) || [];
|
||||
existing.push(participant.purchaseId);
|
||||
userPurchases.set(participant.telegramId, existing);
|
||||
}
|
||||
|
||||
// First pass: Find the winning ticket info
|
||||
let winnerDisplayName = 'Anon';
|
||||
let winnerTicketNumber = '0000';
|
||||
let winnerTelegramId: number | null = null;
|
||||
let prizeSats = 0;
|
||||
let payoutStatus = 'processing';
|
||||
|
||||
for (const [telegramId, purchaseIds] of userPurchases) {
|
||||
for (const purchaseId of purchaseIds) {
|
||||
try {
|
||||
const status = await apiClient.getTicketStatus(purchaseId);
|
||||
if (!status) continue;
|
||||
|
||||
if (status.result.is_winner) {
|
||||
const user = await stateManager.getUser(telegramId);
|
||||
winnerTelegramId = telegramId;
|
||||
winnerDisplayName = user?.displayName || 'Anon';
|
||||
|
||||
const winningTicket = status.tickets.find(t => t.is_winning_ticket);
|
||||
if (winningTicket) {
|
||||
winnerTicketNumber = winningTicket.serial_number.toString().padStart(4, '0');
|
||||
}
|
||||
|
||||
prizeSats = status.result.payout?.amount_sats || cycle.pot_total_sats;
|
||||
payoutStatus = status.result.payout?.status || 'processing';
|
||||
break; // Found winner, stop searching
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Error checking purchase status', { purchaseId, error });
|
||||
}
|
||||
}
|
||||
if (winnerTelegramId) break; // Found winner, stop searching
|
||||
}
|
||||
|
||||
// Second pass: Send ONE notification per user
|
||||
const notifiedUsers = new Set<number>();
|
||||
|
||||
for (const [telegramId] of userPurchases) {
|
||||
if (notifiedUsers.has(telegramId)) continue;
|
||||
notifiedUsers.add(telegramId);
|
||||
|
||||
// Notify each participant about their result
|
||||
for (const participant of participants) {
|
||||
try {
|
||||
const user = await stateManager.getUser(participant.telegramId);
|
||||
if (!user || !user.notifications?.drawResults) continue;
|
||||
const user = await stateManager.getUser(telegramId);
|
||||
if (!user || user.notifications?.drawResults === false) continue;
|
||||
|
||||
// Check if they won
|
||||
const status = await apiClient.getTicketStatus(participant.purchaseId);
|
||||
if (!status) continue;
|
||||
|
||||
const isWinner = status.result.is_winner;
|
||||
const isWinner = telegramId === winnerTelegramId;
|
||||
|
||||
if (isWinner) {
|
||||
// Store winner info for group announcement
|
||||
winnerDisplayName = user.displayName || 'Anon';
|
||||
const winningTicket = status.tickets.find(t => t.is_winning_ticket);
|
||||
if (winningTicket) {
|
||||
winnerTicketNumber = winningTicket.serial_number.toString().padStart(4, '0');
|
||||
}
|
||||
|
||||
// Send winner notification
|
||||
const prizeSats = status.result.payout?.amount_sats || cycle.pot_total_sats;
|
||||
const payoutStatus = status.result.payout?.status || 'processing';
|
||||
await this.bot.sendMessage(
|
||||
participant.telegramId,
|
||||
telegramId,
|
||||
messages.notifications.winner(
|
||||
prizeSats.toLocaleString(),
|
||||
winnerTicketNumber,
|
||||
@@ -383,29 +438,27 @@ class NotificationScheduler {
|
||||
),
|
||||
{ parse_mode: 'Markdown' }
|
||||
);
|
||||
logger.info('Sent winner notification', { telegramId: participant.telegramId });
|
||||
logger.info('Sent winner notification', { telegramId });
|
||||
} else {
|
||||
// Send loser notification
|
||||
// Send loser notification with actual winning ticket number
|
||||
await this.bot.sendMessage(
|
||||
participant.telegramId,
|
||||
telegramId,
|
||||
messages.notifications.loser(
|
||||
winnerTicketNumber,
|
||||
cycle.pot_total_sats.toLocaleString()
|
||||
),
|
||||
{ parse_mode: 'Markdown' }
|
||||
);
|
||||
logger.debug('Sent draw result to participant', { telegramId: participant.telegramId });
|
||||
logger.debug('Sent draw result to participant', { telegramId });
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Failed to notify participant', {
|
||||
telegramId: participant.telegramId,
|
||||
error
|
||||
});
|
||||
logger.error('Failed to notify participant', { telegramId, error });
|
||||
}
|
||||
}
|
||||
|
||||
// Send group announcements (only if there were participants)
|
||||
await this.sendGroupDrawAnnouncements(cycle, winnerDisplayName, winnerTicketNumber, participants.length);
|
||||
// Send group announcements
|
||||
const uniqueUserCount = userPurchases.size;
|
||||
await this.sendGroupDrawAnnouncements(cycle, winnerDisplayName, winnerTicketNumber, uniqueUserCount);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user