2 Commits

Author SHA1 Message Date
Michilis
5bc8c31a7f Add .history folder to .gitignore to prevent committing .env settings 2025-12-25 18:25:40 +00:00
Michilis
d1ce0f0bef 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
2025-12-25 18:03:16 +00:00
6 changed files with 105 additions and 58 deletions

1
.gitignore vendored
View File

@@ -31,6 +31,7 @@ App_info/
# IDE and editor files
.idea/
.vscode/
.history/
*.swp
*.swo
*~

View File

@@ -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;

View File

@@ -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', {

View File

@@ -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

View File

@@ -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

View File

@@ -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
*/