From d1ce0f0beff53bd1ed96e66ab20ca0b2476a5938 Mon Sep 17 00:00:00 2001 From: Michilis Date: Thu, 25 Dec 2025 18:03:16 +0000 Subject: [PATCH] Disable keyboard buttons completely in group chats - Added getReplyMarkupForChat utility to explicitly remove keyboards in groups - Updated all group chat messages to remove keyboards - Prevents keyboards from being re-enabled when users press buttons - Ensures keyboards are disabled for all users in group chats --- telegram_bot/src/handlers/buy.ts | 35 ++++++++++----- telegram_bot/src/handlers/groups.ts | 34 ++++++++++++--- telegram_bot/src/handlers/help.ts | 3 +- telegram_bot/src/index.ts | 66 ++++++++++++----------------- telegram_bot/src/utils/keyboards.ts | 24 +++++++++++ 5 files changed, 104 insertions(+), 58 deletions(-) diff --git a/telegram_bot/src/handlers/buy.ts b/telegram_bot/src/handlers/buy.ts index b9d9bd2..19af617 100644 --- a/telegram_bot/src/handlers/buy.ts +++ b/telegram_bot/src/handlers/buy.ts @@ -10,6 +10,7 @@ import { getViewTicketKeyboard, getMainMenuKeyboard, getCancelKeyboard, + getReplyMarkupForChat, } from '../utils/keyboards'; import { formatSats, formatDate, formatTimeUntil } from '../utils/format'; import { PendingPurchaseData, AwaitingPaymentData } from '../types'; @@ -71,7 +72,9 @@ export async function handleBuyCommand( if (!user.lightningAddress) { await bot.sendMessage(chatId, messages.address.needAddressFirst, { parse_mode: 'Markdown', - reply_markup: getCancelKeyboard(), + reply_markup: isGroupChat(chatId) + ? getReplyMarkupForChat(chatId) + : getCancelKeyboard(), }); await stateManager.updateUserState(userId, 'awaiting_lightning_address'); return; @@ -81,7 +84,10 @@ export async function handleBuyCommand( const jackpot = await apiClient.getNextJackpot(); if (!jackpot) { - await bot.sendMessage(chatId, messages.buy.noActiveJackpot, { parse_mode: 'Markdown' }); + await bot.sendMessage(chatId, messages.buy.noActiveJackpot, { + parse_mode: 'Markdown', + reply_markup: getReplyMarkupForChat(chatId), + }); return; } @@ -96,7 +102,9 @@ export async function handleBuyCommand( await bot.sendMessage(chatId, message, { parse_mode: 'Markdown', - reply_markup: getTicketAmountKeyboard(), + reply_markup: isGroupChat(chatId) + ? getReplyMarkupForChat(chatId) + : getTicketAmountKeyboard(), }); // Store jackpot info in state for later use @@ -195,7 +203,11 @@ export async function handleCustomTicketAmount( await bot.sendMessage( chatId, messages.buy.invalidNumber(config.bot.maxTicketsPerPurchase), - { reply_markup: getCancelKeyboard() } + { + reply_markup: isGroupChat(chatId) + ? getReplyMarkupForChat(chatId) + : getCancelKeyboard(), + } ); return true; } @@ -204,7 +216,11 @@ export async function handleCustomTicketAmount( await bot.sendMessage( chatId, messages.buy.tooManyTickets(config.bot.maxTicketsPerPurchase), - { reply_markup: getCancelKeyboard() } + { + reply_markup: isGroupChat(chatId) + ? getReplyMarkupForChat(chatId) + : getCancelKeyboard(), + } ); return true; } @@ -359,8 +375,7 @@ export async function handlePurchaseConfirmation( } catch (error) { logger.error('Error in handlePurchaseConfirmation', { error, userId }); await bot.sendMessage(chatId, messages.errors.invoiceCreationFailed, { - // Only show reply keyboard in private chats, not in groups - ...(isGroupChat(chatId) ? {} : { reply_markup: getMainMenuKeyboard() }), + reply_markup: getReplyMarkupForChat(chatId, getMainMenuKeyboard()), }); await stateManager.clearUserStateData(userId); } @@ -421,8 +436,7 @@ async function pollPaymentStatus( await bot.sendMessage(chatId, messages.buy.invoiceExpired, { parse_mode: 'Markdown', - // Only show reply keyboard in private chats, not in groups - ...(isGroupChat(chatId) ? {} : { reply_markup: getMainMenuKeyboard() }), + reply_markup: getReplyMarkupForChat(chatId, getMainMenuKeyboard()), }); await stateManager.clearUserStateData(userId); return; @@ -476,8 +490,7 @@ async function pollPaymentStatus( await bot.sendMessage(chatId, messages.buy.invoiceExpiredShort, { parse_mode: 'Markdown', - // Only show reply keyboard in private chats, not in groups - ...(isGroupChat(chatId) ? {} : { reply_markup: getMainMenuKeyboard() }), + reply_markup: getReplyMarkupForChat(chatId, getMainMenuKeyboard()), }); await stateManager.clearUserStateData(userId); return; diff --git a/telegram_bot/src/handlers/groups.ts b/telegram_bot/src/handlers/groups.ts index 12946d6..d613f1a 100644 --- a/telegram_bot/src/handlers/groups.ts +++ b/telegram_bot/src/handlers/groups.ts @@ -2,6 +2,7 @@ import TelegramBot from 'node-telegram-bot-api'; import { groupStateManager } from '../services/groupState'; import { logger, logUserAction } from '../services/logger'; import { messages } from '../messages'; +import { getReplyMarkupForChat } from '../utils/keyboards'; import { GroupSettings, REMINDER_PRESETS, @@ -52,6 +53,7 @@ export async function handleBotAddedToGroup( await bot.sendMessage(chatId, messages.groups.welcome(chatTitle), { parse_mode: 'Markdown', + reply_markup: getReplyMarkupForChat(chatId), }); } catch (error) { logger.error('Failed to register group', { error, chatId }); @@ -97,7 +99,9 @@ export async function handleGroupSettings( // Check if user is admin const isAdmin = await isGroupAdmin(bot, chatId, userId); if (!isAdmin) { - await bot.sendMessage(chatId, messages.groups.adminOnly); + await bot.sendMessage(chatId, messages.groups.adminOnly, { + reply_markup: getReplyMarkupForChat(chatId), + }); return; } @@ -117,7 +121,9 @@ export async function handleGroupSettings( const currentSettings = settings || await groupStateManager.getGroup(chatId); if (!currentSettings) { - await bot.sendMessage(chatId, messages.errors.generic); + await bot.sendMessage(chatId, messages.errors.generic, { + reply_markup: getReplyMarkupForChat(chatId), + }); return; } @@ -138,19 +144,27 @@ export async function handleGroupSettings( // Notify in the group that settings were sent to DM await bot.sendMessage( chatId, - `⚙️ @${msg.from?.username || 'Admin'}, I've sent the group settings to your DMs!` + `⚙️ @${msg.from?.username || 'Admin'}, I've sent the group settings to your DMs!`, + { + reply_markup: getReplyMarkupForChat(chatId), + } ); } catch (dmError) { // If DM fails (user hasn't started the bot), prompt them to start it logger.warn('Failed to send settings DM', { error: dmError, userId }); await bot.sendMessage( chatId, - `⚙️ @${msg.from?.username || 'Admin'}, please start a private chat with me first (@LightningLottoBot) so I can send you the settings.` + `⚙️ @${msg.from?.username || 'Admin'}, please start a private chat with me first (@LightningLottoBot) so I can send you the settings.`, + { + reply_markup: getReplyMarkupForChat(chatId), + } ); } } catch (error) { logger.error('Error in handleGroupSettings', { error, chatId }); - await bot.sendMessage(chatId, messages.errors.generic); + await bot.sendMessage(chatId, messages.errors.generic, { + reply_markup: getReplyMarkupForChat(chatId), + }); } } @@ -583,7 +597,10 @@ export async function broadcastDrawAnnouncement( for (const group of groups) { try { - await bot.sendMessage(group.groupId, announcement, { parse_mode: 'Markdown' }); + await bot.sendMessage(group.groupId, announcement, { + parse_mode: 'Markdown', + reply_markup: getReplyMarkupForChat(group.groupId), + }); sent++; } catch (error) { logger.error('Failed to send announcement to group', { @@ -613,7 +630,10 @@ export async function broadcastDrawReminder( for (const group of groups) { try { - await bot.sendMessage(group.groupId, reminder, { parse_mode: 'Markdown' }); + await bot.sendMessage(group.groupId, reminder, { + parse_mode: 'Markdown', + reply_markup: getReplyMarkupForChat(group.groupId), + }); sent++; } catch (error) { logger.error('Failed to send reminder to group', { diff --git a/telegram_bot/src/handlers/help.ts b/telegram_bot/src/handlers/help.ts index fa53cea..0166c16 100644 --- a/telegram_bot/src/handlers/help.ts +++ b/telegram_bot/src/handlers/help.ts @@ -1,6 +1,6 @@ import TelegramBot from 'node-telegram-bot-api'; import { logUserAction } from '../services/logger'; -import { getMainMenuKeyboard } from '../utils/keyboards'; +import { getMainMenuKeyboard, getReplyMarkupForChat } from '../utils/keyboards'; import { messages } from '../messages'; /** @@ -22,6 +22,7 @@ export async function handleHelpCommand( // Show group-specific help with admin commands await bot.sendMessage(chatId, messages.help.groupMessage, { parse_mode: 'Markdown', + reply_markup: getReplyMarkupForChat(chatId), }); } else { // Show user help in DM diff --git a/telegram_bot/src/index.ts b/telegram_bot/src/index.ts index 5eb7689..707ef2e 100644 --- a/telegram_bot/src/index.ts +++ b/telegram_bot/src/index.ts @@ -33,7 +33,7 @@ import { handleGroupSettingsCallback, handleGroupRefresh, } from './handlers'; -import { getMainMenuKeyboard } from './utils/keyboards'; +import { getMainMenuKeyboard, getReplyMarkupForChat } from './utils/keyboards'; import { messages } from './messages'; import { formatSats, formatDate, formatTimeUntil } from './utils/format'; @@ -93,12 +93,15 @@ bot.on('message', async (msg) => { bot.onText(/\/start/, async (msg) => { if (!shouldProcessMessage(msg.message_id)) return; - // In groups, just show a welcome message + // In groups, just show a welcome message (explicitly remove keyboard) if (isGroupChat(msg)) { await bot.sendMessage( msg.chat.id, `⚡ *Lightning Jackpot Bot*\n\nTo buy tickets and manage your account, message me directly!\n\nUse /jackpot to see current jackpot info.\nUse /lottohelp for commands.\nAdmins: Use /lottosettings to configure the bot.`, - { parse_mode: 'Markdown' } + { + parse_mode: 'Markdown', + reply_markup: getReplyMarkupForChat(msg.chat.id), + } ); return; } @@ -116,6 +119,7 @@ bot.onText(/\/buyticket/, async (msg) => { if (settings && !settings.ticketPurchaseAllowed) { await bot.sendMessage(msg.chat.id, messages.groups.purchasesDisabled, { parse_mode: 'Markdown', + reply_markup: getReplyMarkupForChat(msg.chat.id), }); return; } @@ -130,7 +134,9 @@ bot.onText(/\/tickets/, async (msg) => { // Only in private chat if (isGroupChat(msg)) { - await bot.sendMessage(msg.chat.id, '🧾 To view your tickets, message me directly!'); + await bot.sendMessage(msg.chat.id, '🧾 To view your tickets, message me directly!', { + reply_markup: getReplyMarkupForChat(msg.chat.id), + }); return; } @@ -143,7 +149,9 @@ bot.onText(/\/wins/, async (msg) => { // Only in private chat if (isGroupChat(msg)) { - await bot.sendMessage(msg.chat.id, '🏆 To view your wins, message me directly!'); + await bot.sendMessage(msg.chat.id, '🏆 To view your wins, message me directly!', { + reply_markup: getReplyMarkupForChat(msg.chat.id), + }); return; } @@ -156,7 +164,9 @@ bot.onText(/\/lottoaddress/, async (msg) => { // Only in private chat if (isGroupChat(msg)) { - await bot.sendMessage(msg.chat.id, '⚡ To update your Lightning Address, message me directly!'); + await bot.sendMessage(msg.chat.id, '⚡ To update your Lightning Address, message me directly!', { + reply_markup: getReplyMarkupForChat(msg.chat.id), + }); return; } @@ -169,7 +179,9 @@ bot.onText(/\/lottomenu/, async (msg) => { // Only in private chat if (isGroupChat(msg)) { - await bot.sendMessage(msg.chat.id, '📱 To access the full menu, message me directly!'); + await bot.sendMessage(msg.chat.id, '📱 To access the full menu, message me directly!', { + reply_markup: getReplyMarkupForChat(msg.chat.id), + }); return; } @@ -206,7 +218,10 @@ bot.onText(/\/jackpot/, async (msg) => { Use /buyticket to get your tickets! 🍀`; - await bot.sendMessage(msg.chat.id, message, { parse_mode: 'Markdown' }); + await bot.sendMessage(msg.chat.id, message, { + parse_mode: 'Markdown', + reply_markup: getReplyMarkupForChat(msg.chat.id), + }); } catch (error) { logger.error('Error in /jackpot command', { error }); await bot.sendMessage(msg.chat.id, messages.errors.systemUnavailable); @@ -223,45 +238,18 @@ bot.onText(/\/lottosettings/, async (msg) => { // TEXT MESSAGES // ═══════════════════════════════════════════════════════════════════════════ -// Keyboard button texts that should trigger keyboard removal in groups -const KEYBOARD_BUTTON_TEXTS = [ - '🎰 Upcoming Jackpot', - '🎟 Buy Tickets', - '🧾 My Tickets', - '🏆 My Wins', - '⚡ Lightning Address', - '⚙️ Settings', - 'ℹ️ Help', -]; - // Handle keyboard button presses (text messages) bot.on('message', async (msg) => { if (!msg.text || msg.text.startsWith('/')) return; if (!shouldProcessMessage(msg.message_id)) return; + // Ignore group messages for button handling + // Keyboards are explicitly removed in all group chat messages + if (isGroupChat(msg)) return; + const text = msg.text.trim(); const userId = msg.from?.id; - // Handle keyboard button presses in groups - remove the keyboard for that user - if (isGroupChat(msg)) { - // Only trigger if the message matches an exact keyboard button text - if (KEYBOARD_BUTTON_TEXTS.includes(text)) { - try { - // Send a temporary message to remove the keyboard for this user only - const sentMessage = await bot.sendMessage(msg.chat.id, '⚡', { - reply_to_message_id: msg.message_id, - reply_markup: { remove_keyboard: true, selective: true }, - }); - // Delete the message immediately so nothing is visible in the group - await bot.deleteMessage(msg.chat.id, sentMessage.message_id); - } catch (error) { - // Silently fail - bot might not have delete permissions - logger.error('Error removing keyboard in group', { error, chatId: msg.chat.id, userId }); - } - } - return; - } - if (!userId) return; // Handle menu button presses diff --git a/telegram_bot/src/utils/keyboards.ts b/telegram_bot/src/utils/keyboards.ts index 22ededa..fafe326 100644 --- a/telegram_bot/src/utils/keyboards.ts +++ b/telegram_bot/src/utils/keyboards.ts @@ -1,9 +1,33 @@ import TelegramBot, { InlineKeyboardMarkup, ReplyKeyboardMarkup, + ReplyKeyboardRemove, } from 'node-telegram-bot-api'; import { NotificationPreferences } from '../types'; +/** + * Check if chat ID is a group chat (negative IDs are groups/supergroups) + */ +function isGroupChat(chatId: number): boolean { + return chatId < 0; +} + +/** + * Get reply markup for a chat - removes keyboard in group chats, shows keyboard in private chats + * This ensures keyboards are completely disabled in group chats + */ +export function getReplyMarkupForChat( + chatId: number, + keyboard?: ReplyKeyboardMarkup +): ReplyKeyboardMarkup | ReplyKeyboardRemove | undefined { + if (isGroupChat(chatId)) { + // Explicitly remove keyboard in group chats + return { remove_keyboard: true }; + } + // Return the provided keyboard for private chats, or undefined if none provided + return keyboard; +} + /** * Main menu reply keyboard */