Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5bc8c31a7f | ||
|
|
d1ce0f0bef | ||
|
|
ff9c1f1dcf |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -31,6 +31,7 @@ App_info/
|
|||||||
# IDE and editor files
|
# IDE and editor files
|
||||||
.idea/
|
.idea/
|
||||||
.vscode/
|
.vscode/
|
||||||
|
.history/
|
||||||
*.swp
|
*.swp
|
||||||
*.swo
|
*.swo
|
||||||
*~
|
*~
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
getViewTicketKeyboard,
|
getViewTicketKeyboard,
|
||||||
getMainMenuKeyboard,
|
getMainMenuKeyboard,
|
||||||
getCancelKeyboard,
|
getCancelKeyboard,
|
||||||
|
getReplyMarkupForChat,
|
||||||
} from '../utils/keyboards';
|
} from '../utils/keyboards';
|
||||||
import { formatSats, formatDate, formatTimeUntil } from '../utils/format';
|
import { formatSats, formatDate, formatTimeUntil } from '../utils/format';
|
||||||
import { PendingPurchaseData, AwaitingPaymentData } from '../types';
|
import { PendingPurchaseData, AwaitingPaymentData } from '../types';
|
||||||
@@ -71,7 +72,9 @@ export async function handleBuyCommand(
|
|||||||
if (!user.lightningAddress) {
|
if (!user.lightningAddress) {
|
||||||
await bot.sendMessage(chatId, messages.address.needAddressFirst, {
|
await bot.sendMessage(chatId, messages.address.needAddressFirst, {
|
||||||
parse_mode: 'Markdown',
|
parse_mode: 'Markdown',
|
||||||
reply_markup: getCancelKeyboard(),
|
reply_markup: isGroupChat(chatId)
|
||||||
|
? getReplyMarkupForChat(chatId)
|
||||||
|
: getCancelKeyboard(),
|
||||||
});
|
});
|
||||||
await stateManager.updateUserState(userId, 'awaiting_lightning_address');
|
await stateManager.updateUserState(userId, 'awaiting_lightning_address');
|
||||||
return;
|
return;
|
||||||
@@ -81,7 +84,10 @@ export async function handleBuyCommand(
|
|||||||
const jackpot = await apiClient.getNextJackpot();
|
const jackpot = await apiClient.getNextJackpot();
|
||||||
|
|
||||||
if (!jackpot) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,7 +102,9 @@ export async function handleBuyCommand(
|
|||||||
|
|
||||||
await bot.sendMessage(chatId, message, {
|
await bot.sendMessage(chatId, message, {
|
||||||
parse_mode: 'Markdown',
|
parse_mode: 'Markdown',
|
||||||
reply_markup: getTicketAmountKeyboard(),
|
reply_markup: isGroupChat(chatId)
|
||||||
|
? getReplyMarkupForChat(chatId)
|
||||||
|
: getTicketAmountKeyboard(),
|
||||||
});
|
});
|
||||||
|
|
||||||
// Store jackpot info in state for later use
|
// Store jackpot info in state for later use
|
||||||
@@ -195,7 +203,11 @@ export async function handleCustomTicketAmount(
|
|||||||
await bot.sendMessage(
|
await bot.sendMessage(
|
||||||
chatId,
|
chatId,
|
||||||
messages.buy.invalidNumber(config.bot.maxTicketsPerPurchase),
|
messages.buy.invalidNumber(config.bot.maxTicketsPerPurchase),
|
||||||
{ reply_markup: getCancelKeyboard() }
|
{
|
||||||
|
reply_markup: isGroupChat(chatId)
|
||||||
|
? getReplyMarkupForChat(chatId)
|
||||||
|
: getCancelKeyboard(),
|
||||||
|
}
|
||||||
);
|
);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -204,7 +216,11 @@ export async function handleCustomTicketAmount(
|
|||||||
await bot.sendMessage(
|
await bot.sendMessage(
|
||||||
chatId,
|
chatId,
|
||||||
messages.buy.tooManyTickets(config.bot.maxTicketsPerPurchase),
|
messages.buy.tooManyTickets(config.bot.maxTicketsPerPurchase),
|
||||||
{ reply_markup: getCancelKeyboard() }
|
{
|
||||||
|
reply_markup: isGroupChat(chatId)
|
||||||
|
? getReplyMarkupForChat(chatId)
|
||||||
|
: getCancelKeyboard(),
|
||||||
|
}
|
||||||
);
|
);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -359,8 +375,7 @@ export async function handlePurchaseConfirmation(
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Error in handlePurchaseConfirmation', { error, userId });
|
logger.error('Error in handlePurchaseConfirmation', { error, userId });
|
||||||
await bot.sendMessage(chatId, messages.errors.invoiceCreationFailed, {
|
await bot.sendMessage(chatId, messages.errors.invoiceCreationFailed, {
|
||||||
// Only show reply keyboard in private chats, not in groups
|
reply_markup: getReplyMarkupForChat(chatId, getMainMenuKeyboard()),
|
||||||
...(isGroupChat(chatId) ? {} : { reply_markup: getMainMenuKeyboard() }),
|
|
||||||
});
|
});
|
||||||
await stateManager.clearUserStateData(userId);
|
await stateManager.clearUserStateData(userId);
|
||||||
}
|
}
|
||||||
@@ -421,8 +436,7 @@ async function pollPaymentStatus(
|
|||||||
|
|
||||||
await bot.sendMessage(chatId, messages.buy.invoiceExpired, {
|
await bot.sendMessage(chatId, messages.buy.invoiceExpired, {
|
||||||
parse_mode: 'Markdown',
|
parse_mode: 'Markdown',
|
||||||
// Only show reply keyboard in private chats, not in groups
|
reply_markup: getReplyMarkupForChat(chatId, getMainMenuKeyboard()),
|
||||||
...(isGroupChat(chatId) ? {} : { reply_markup: getMainMenuKeyboard() }),
|
|
||||||
});
|
});
|
||||||
await stateManager.clearUserStateData(userId);
|
await stateManager.clearUserStateData(userId);
|
||||||
return;
|
return;
|
||||||
@@ -476,8 +490,7 @@ async function pollPaymentStatus(
|
|||||||
|
|
||||||
await bot.sendMessage(chatId, messages.buy.invoiceExpiredShort, {
|
await bot.sendMessage(chatId, messages.buy.invoiceExpiredShort, {
|
||||||
parse_mode: 'Markdown',
|
parse_mode: 'Markdown',
|
||||||
// Only show reply keyboard in private chats, not in groups
|
reply_markup: getReplyMarkupForChat(chatId, getMainMenuKeyboard()),
|
||||||
...(isGroupChat(chatId) ? {} : { reply_markup: getMainMenuKeyboard() }),
|
|
||||||
});
|
});
|
||||||
await stateManager.clearUserStateData(userId);
|
await stateManager.clearUserStateData(userId);
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import TelegramBot from 'node-telegram-bot-api';
|
|||||||
import { groupStateManager } from '../services/groupState';
|
import { groupStateManager } from '../services/groupState';
|
||||||
import { logger, logUserAction } from '../services/logger';
|
import { logger, logUserAction } from '../services/logger';
|
||||||
import { messages } from '../messages';
|
import { messages } from '../messages';
|
||||||
|
import { getReplyMarkupForChat } from '../utils/keyboards';
|
||||||
import {
|
import {
|
||||||
GroupSettings,
|
GroupSettings,
|
||||||
REMINDER_PRESETS,
|
REMINDER_PRESETS,
|
||||||
@@ -52,6 +53,7 @@ export async function handleBotAddedToGroup(
|
|||||||
|
|
||||||
await bot.sendMessage(chatId, messages.groups.welcome(chatTitle), {
|
await bot.sendMessage(chatId, messages.groups.welcome(chatTitle), {
|
||||||
parse_mode: 'Markdown',
|
parse_mode: 'Markdown',
|
||||||
|
reply_markup: getReplyMarkupForChat(chatId),
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Failed to register group', { error, chatId });
|
logger.error('Failed to register group', { error, chatId });
|
||||||
@@ -97,7 +99,9 @@ export async function handleGroupSettings(
|
|||||||
// Check if user is admin
|
// Check if user is admin
|
||||||
const isAdmin = await isGroupAdmin(bot, chatId, userId);
|
const isAdmin = await isGroupAdmin(bot, chatId, userId);
|
||||||
if (!isAdmin) {
|
if (!isAdmin) {
|
||||||
await bot.sendMessage(chatId, messages.groups.adminOnly);
|
await bot.sendMessage(chatId, messages.groups.adminOnly, {
|
||||||
|
reply_markup: getReplyMarkupForChat(chatId),
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,7 +121,9 @@ export async function handleGroupSettings(
|
|||||||
|
|
||||||
const currentSettings = settings || await groupStateManager.getGroup(chatId);
|
const currentSettings = settings || await groupStateManager.getGroup(chatId);
|
||||||
if (!currentSettings) {
|
if (!currentSettings) {
|
||||||
await bot.sendMessage(chatId, messages.errors.generic);
|
await bot.sendMessage(chatId, messages.errors.generic, {
|
||||||
|
reply_markup: getReplyMarkupForChat(chatId),
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -138,19 +144,27 @@ export async function handleGroupSettings(
|
|||||||
// Notify in the group that settings were sent to DM
|
// Notify in the group that settings were sent to DM
|
||||||
await bot.sendMessage(
|
await bot.sendMessage(
|
||||||
chatId,
|
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) {
|
} catch (dmError) {
|
||||||
// If DM fails (user hasn't started the bot), prompt them to start it
|
// If DM fails (user hasn't started the bot), prompt them to start it
|
||||||
logger.warn('Failed to send settings DM', { error: dmError, userId });
|
logger.warn('Failed to send settings DM', { error: dmError, userId });
|
||||||
await bot.sendMessage(
|
await bot.sendMessage(
|
||||||
chatId,
|
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) {
|
} catch (error) {
|
||||||
logger.error('Error in handleGroupSettings', { error, chatId });
|
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) {
|
for (const group of groups) {
|
||||||
try {
|
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++;
|
sent++;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Failed to send announcement to group', {
|
logger.error('Failed to send announcement to group', {
|
||||||
@@ -613,7 +630,10 @@ export async function broadcastDrawReminder(
|
|||||||
|
|
||||||
for (const group of groups) {
|
for (const group of groups) {
|
||||||
try {
|
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++;
|
sent++;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Failed to send reminder to group', {
|
logger.error('Failed to send reminder to group', {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import TelegramBot from 'node-telegram-bot-api';
|
import TelegramBot from 'node-telegram-bot-api';
|
||||||
import { logUserAction } from '../services/logger';
|
import { logUserAction } from '../services/logger';
|
||||||
import { getMainMenuKeyboard } from '../utils/keyboards';
|
import { getMainMenuKeyboard, getReplyMarkupForChat } from '../utils/keyboards';
|
||||||
import { messages } from '../messages';
|
import { messages } from '../messages';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -22,6 +22,7 @@ export async function handleHelpCommand(
|
|||||||
// Show group-specific help with admin commands
|
// Show group-specific help with admin commands
|
||||||
await bot.sendMessage(chatId, messages.help.groupMessage, {
|
await bot.sendMessage(chatId, messages.help.groupMessage, {
|
||||||
parse_mode: 'Markdown',
|
parse_mode: 'Markdown',
|
||||||
|
reply_markup: getReplyMarkupForChat(chatId),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// Show user help in DM
|
// Show user help in DM
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ import {
|
|||||||
handleGroupSettingsCallback,
|
handleGroupSettingsCallback,
|
||||||
handleGroupRefresh,
|
handleGroupRefresh,
|
||||||
} from './handlers';
|
} from './handlers';
|
||||||
import { getMainMenuKeyboard } from './utils/keyboards';
|
import { getMainMenuKeyboard, getReplyMarkupForChat } from './utils/keyboards';
|
||||||
import { messages } from './messages';
|
import { messages } from './messages';
|
||||||
import { formatSats, formatDate, formatTimeUntil } from './utils/format';
|
import { formatSats, formatDate, formatTimeUntil } from './utils/format';
|
||||||
|
|
||||||
@@ -93,12 +93,15 @@ bot.on('message', async (msg) => {
|
|||||||
bot.onText(/\/start/, async (msg) => {
|
bot.onText(/\/start/, async (msg) => {
|
||||||
if (!shouldProcessMessage(msg.message_id)) return;
|
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)) {
|
if (isGroupChat(msg)) {
|
||||||
await bot.sendMessage(
|
await bot.sendMessage(
|
||||||
msg.chat.id,
|
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.`,
|
`⚡ *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;
|
return;
|
||||||
}
|
}
|
||||||
@@ -116,6 +119,7 @@ bot.onText(/\/buyticket/, async (msg) => {
|
|||||||
if (settings && !settings.ticketPurchaseAllowed) {
|
if (settings && !settings.ticketPurchaseAllowed) {
|
||||||
await bot.sendMessage(msg.chat.id, messages.groups.purchasesDisabled, {
|
await bot.sendMessage(msg.chat.id, messages.groups.purchasesDisabled, {
|
||||||
parse_mode: 'Markdown',
|
parse_mode: 'Markdown',
|
||||||
|
reply_markup: getReplyMarkupForChat(msg.chat.id),
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -130,7 +134,9 @@ bot.onText(/\/tickets/, async (msg) => {
|
|||||||
|
|
||||||
// Only in private chat
|
// Only in private chat
|
||||||
if (isGroupChat(msg)) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -143,7 +149,9 @@ bot.onText(/\/wins/, async (msg) => {
|
|||||||
|
|
||||||
// Only in private chat
|
// Only in private chat
|
||||||
if (isGroupChat(msg)) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,7 +164,9 @@ bot.onText(/\/lottoaddress/, async (msg) => {
|
|||||||
|
|
||||||
// Only in private chat
|
// Only in private chat
|
||||||
if (isGroupChat(msg)) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -169,7 +179,9 @@ bot.onText(/\/lottomenu/, async (msg) => {
|
|||||||
|
|
||||||
// Only in private chat
|
// Only in private chat
|
||||||
if (isGroupChat(msg)) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -206,7 +218,10 @@ bot.onText(/\/jackpot/, async (msg) => {
|
|||||||
|
|
||||||
Use /buyticket to get your tickets! 🍀`;
|
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) {
|
} catch (error) {
|
||||||
logger.error('Error in /jackpot command', { error });
|
logger.error('Error in /jackpot command', { error });
|
||||||
await bot.sendMessage(msg.chat.id, messages.errors.systemUnavailable);
|
await bot.sendMessage(msg.chat.id, messages.errors.systemUnavailable);
|
||||||
@@ -229,6 +244,7 @@ bot.on('message', async (msg) => {
|
|||||||
if (!shouldProcessMessage(msg.message_id)) return;
|
if (!shouldProcessMessage(msg.message_id)) return;
|
||||||
|
|
||||||
// Ignore group messages for button handling
|
// Ignore group messages for button handling
|
||||||
|
// Keyboards are explicitly removed in all group chat messages
|
||||||
if (isGroupChat(msg)) return;
|
if (isGroupChat(msg)) return;
|
||||||
|
|
||||||
const text = msg.text.trim();
|
const text = msg.text.trim();
|
||||||
|
|||||||
@@ -1,9 +1,33 @@
|
|||||||
import TelegramBot, {
|
import TelegramBot, {
|
||||||
InlineKeyboardMarkup,
|
InlineKeyboardMarkup,
|
||||||
ReplyKeyboardMarkup,
|
ReplyKeyboardMarkup,
|
||||||
|
ReplyKeyboardRemove,
|
||||||
} from 'node-telegram-bot-api';
|
} from 'node-telegram-bot-api';
|
||||||
import { NotificationPreferences } from '../types';
|
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
|
* Main menu reply keyboard
|
||||||
*/
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user