feat(telegram): improve group handling and default display name to @username
- /lottosettings now opens settings in private DM instead of group - Bot only reacts to / commands in groups (keyboard buttons ignored) - Reply keyboard buttons removed from group messages - Default display name now uses @username instead of 'Anon' - Users can still manually update display name in settings - Updated all display name usages to use centralized getDisplayName()
This commit is contained in:
@@ -15,6 +15,13 @@ import { formatSats, formatDate, formatTimeUntil } from '../utils/format';
|
||||
import { PendingPurchaseData, AwaitingPaymentData } from '../types';
|
||||
import { messages } from '../messages';
|
||||
|
||||
/**
|
||||
* Check if chat is a group (negative ID for supergroups, or check type)
|
||||
*/
|
||||
function isGroupChat(chatId: number): boolean {
|
||||
return chatId < 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle /buy command or "Buy Tickets" button
|
||||
*/
|
||||
@@ -293,11 +300,11 @@ export async function handlePurchaseConfirmation(
|
||||
|
||||
logUserAction(userId, 'Confirmed purchase', { tickets: pendingData.ticketCount });
|
||||
|
||||
// Create invoice with user's display name
|
||||
// Create invoice with user's display name (defaults to @username)
|
||||
const purchaseResult = await apiClient.buyTickets(
|
||||
pendingData.ticketCount,
|
||||
user.lightningAddress,
|
||||
user.displayName || 'Anon'
|
||||
stateManager.getDisplayName(user)
|
||||
);
|
||||
|
||||
logPaymentEvent(userId, purchaseResult.ticket_purchase_id, 'created', {
|
||||
@@ -352,7 +359,8 @@ export async function handlePurchaseConfirmation(
|
||||
} catch (error) {
|
||||
logger.error('Error in handlePurchaseConfirmation', { error, userId });
|
||||
await bot.sendMessage(chatId, messages.errors.invoiceCreationFailed, {
|
||||
reply_markup: getMainMenuKeyboard(),
|
||||
// Only show reply keyboard in private chats, not in groups
|
||||
...(isGroupChat(chatId) ? {} : { reply_markup: getMainMenuKeyboard() }),
|
||||
});
|
||||
await stateManager.clearUserStateData(userId);
|
||||
}
|
||||
@@ -413,7 +421,8 @@ async function pollPaymentStatus(
|
||||
|
||||
await bot.sendMessage(chatId, messages.buy.invoiceExpired, {
|
||||
parse_mode: 'Markdown',
|
||||
reply_markup: getMainMenuKeyboard(),
|
||||
// Only show reply keyboard in private chats, not in groups
|
||||
...(isGroupChat(chatId) ? {} : { reply_markup: getMainMenuKeyboard() }),
|
||||
});
|
||||
await stateManager.clearUserStateData(userId);
|
||||
return;
|
||||
@@ -467,7 +476,8 @@ async function pollPaymentStatus(
|
||||
|
||||
await bot.sendMessage(chatId, messages.buy.invoiceExpiredShort, {
|
||||
parse_mode: 'Markdown',
|
||||
reply_markup: getMainMenuKeyboard(),
|
||||
// Only show reply keyboard in private chats, not in groups
|
||||
...(isGroupChat(chatId) ? {} : { reply_markup: getMainMenuKeyboard() }),
|
||||
});
|
||||
await stateManager.clearUserStateData(userId);
|
||||
return;
|
||||
|
||||
@@ -77,6 +77,7 @@ export async function handleBotRemovedFromGroup(
|
||||
|
||||
/**
|
||||
* Handle /settings command (group admin only)
|
||||
* Opens settings in private DM, not in the group
|
||||
*/
|
||||
export async function handleGroupSettings(
|
||||
bot: TelegramBot,
|
||||
@@ -120,8 +121,10 @@ export async function handleGroupSettings(
|
||||
return;
|
||||
}
|
||||
|
||||
// Send settings to user's private DM
|
||||
try {
|
||||
const sentMessage = await bot.sendMessage(
|
||||
chatId,
|
||||
userId,
|
||||
messages.groups.settingsOverview(currentSettings),
|
||||
{
|
||||
parse_mode: 'Markdown',
|
||||
@@ -130,7 +133,21 @@ export async function handleGroupSettings(
|
||||
);
|
||||
|
||||
// Schedule auto-delete after 2 minutes
|
||||
scheduleSettingsMessageDeletion(bot, chatId, sentMessage.message_id);
|
||||
scheduleSettingsMessageDeletion(bot, userId, sentMessage.message_id);
|
||||
|
||||
// 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!`
|
||||
);
|
||||
} 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.`
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Error in handleGroupSettings', { error, chatId });
|
||||
await bot.sendMessage(chatId, messages.errors.generic);
|
||||
@@ -167,22 +184,45 @@ function scheduleSettingsMessageDeletion(
|
||||
settingsMessageTimeouts.set(key, timeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract group ID from the end of a callback action string
|
||||
* e.g., "toggle_enabled_-123456789" -> { action: "toggle_enabled", groupId: -123456789 }
|
||||
*/
|
||||
function parseGroupAction(fullAction: string): { action: string; groupId: number } | null {
|
||||
// Match action followed by underscore and group ID (negative or positive number)
|
||||
const match = fullAction.match(/^(.+)_(-?\d+)$/);
|
||||
if (!match) return null;
|
||||
const groupId = parseInt(match[2], 10);
|
||||
if (isNaN(groupId)) return null;
|
||||
return { action: match[1], groupId };
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle group settings toggle callback
|
||||
* Settings can now be managed from DMs, so group ID is included in callback data
|
||||
*/
|
||||
export async function handleGroupSettingsCallback(
|
||||
bot: TelegramBot,
|
||||
query: TelegramBot.CallbackQuery,
|
||||
action: string
|
||||
): Promise<void> {
|
||||
const chatId = query.message?.chat.id;
|
||||
const dmChatId = query.message?.chat.id;
|
||||
const userId = query.from.id;
|
||||
const messageId = query.message?.message_id;
|
||||
|
||||
if (!chatId || !messageId) return;
|
||||
if (!dmChatId || !messageId) return;
|
||||
|
||||
// Check if user is admin
|
||||
const isAdmin = await isGroupAdmin(bot, chatId, userId);
|
||||
// Parse the action to extract the group ID
|
||||
const parsed = parseGroupAction(action);
|
||||
if (!parsed) {
|
||||
await bot.answerCallbackQuery(query.id, { text: 'Invalid action' });
|
||||
return;
|
||||
}
|
||||
|
||||
const { action: settingAction, groupId } = parsed;
|
||||
|
||||
// Check if user is admin of the target group
|
||||
const isAdmin = await isGroupAdmin(bot, groupId, userId);
|
||||
if (!isAdmin) {
|
||||
await bot.answerCallbackQuery(query.id, {
|
||||
text: messages.groups.adminOnly,
|
||||
@@ -191,11 +231,11 @@ export async function handleGroupSettingsCallback(
|
||||
return;
|
||||
}
|
||||
|
||||
// Refresh auto-delete timer on any interaction
|
||||
scheduleSettingsMessageDeletion(bot, chatId, messageId);
|
||||
// Refresh auto-delete timer on any interaction (in the DM)
|
||||
scheduleSettingsMessageDeletion(bot, dmChatId, messageId);
|
||||
|
||||
try {
|
||||
const currentSettings = await groupStateManager.getGroup(chatId);
|
||||
const currentSettings = await groupStateManager.getGroup(groupId);
|
||||
if (!currentSettings) {
|
||||
await bot.answerCallbackQuery(query.id, { text: 'Group not found' });
|
||||
return;
|
||||
@@ -204,10 +244,10 @@ export async function handleGroupSettingsCallback(
|
||||
let updatedSettings: GroupSettings | null = null;
|
||||
|
||||
// Handle toggle actions
|
||||
if (action.startsWith('toggle_')) {
|
||||
if (settingAction.startsWith('toggle_')) {
|
||||
let setting: 'enabled' | 'drawAnnouncements' | 'reminders' | 'ticketPurchaseAllowed' | 'newJackpotAnnouncement' | 'reminder1Enabled' | 'reminder2Enabled' | 'reminder3Enabled';
|
||||
|
||||
switch (action) {
|
||||
switch (settingAction) {
|
||||
case 'toggle_enabled':
|
||||
setting = 'enabled';
|
||||
break;
|
||||
@@ -239,11 +279,11 @@ export async function handleGroupSettingsCallback(
|
||||
|
||||
const currentValue = currentSettings[setting] !== false; // Default true for new settings
|
||||
const newValue = !currentValue;
|
||||
updatedSettings = await groupStateManager.updateSetting(chatId, setting, newValue);
|
||||
updatedSettings = await groupStateManager.updateSetting(groupId, setting, newValue);
|
||||
|
||||
if (updatedSettings) {
|
||||
logUserAction(userId, 'Updated group setting', {
|
||||
groupId: chatId,
|
||||
groupId,
|
||||
setting,
|
||||
newValue,
|
||||
});
|
||||
@@ -255,13 +295,14 @@ export async function handleGroupSettingsCallback(
|
||||
|
||||
// Legacy handlers removed - now using 3-slot reminder system with toggle_reminder1/2/3 and time adjustments
|
||||
|
||||
// Handle announcement delay selection
|
||||
if (action.startsWith('announce_delay_')) {
|
||||
const seconds = parseInt(action.replace('announce_delay_', ''), 10);
|
||||
// Handle announcement delay selection (announce_delay_30 -> seconds=30)
|
||||
const announceDelayMatch = settingAction.match(/^announce_delay_(\d+)$/);
|
||||
if (announceDelayMatch) {
|
||||
const seconds = parseInt(announceDelayMatch[1], 10);
|
||||
if (!isNaN(seconds)) {
|
||||
updatedSettings = await groupStateManager.updateAnnouncementDelay(chatId, seconds);
|
||||
updatedSettings = await groupStateManager.updateAnnouncementDelay(groupId, seconds);
|
||||
if (updatedSettings) {
|
||||
logUserAction(userId, 'Updated announcement delay', { groupId: chatId, seconds });
|
||||
logUserAction(userId, 'Updated announcement delay', { groupId, seconds });
|
||||
await bot.answerCallbackQuery(query.id, {
|
||||
text: seconds === 0 ? 'Announce immediately' : `Announce ${seconds}s after draw`
|
||||
});
|
||||
@@ -269,13 +310,14 @@ export async function handleGroupSettingsCallback(
|
||||
}
|
||||
}
|
||||
|
||||
// Handle new jackpot delay selection
|
||||
if (action.startsWith('newjackpot_delay_')) {
|
||||
const minutes = parseInt(action.replace('newjackpot_delay_', ''), 10);
|
||||
// Handle new jackpot delay selection (newjackpot_delay_5 -> minutes=5)
|
||||
const newJackpotDelayMatch = settingAction.match(/^newjackpot_delay_(\d+)$/);
|
||||
if (newJackpotDelayMatch) {
|
||||
const minutes = parseInt(newJackpotDelayMatch[1], 10);
|
||||
if (!isNaN(minutes)) {
|
||||
updatedSettings = await groupStateManager.updateNewJackpotDelay(chatId, minutes);
|
||||
updatedSettings = await groupStateManager.updateNewJackpotDelay(groupId, minutes);
|
||||
if (updatedSettings) {
|
||||
logUserAction(userId, 'Updated new jackpot delay', { groupId: chatId, minutes });
|
||||
logUserAction(userId, 'Updated new jackpot delay', { groupId, minutes });
|
||||
await bot.answerCallbackQuery(query.id, {
|
||||
text: minutes === 0 ? 'Announce immediately' : `Announce ${minutes} min after new jackpot`
|
||||
});
|
||||
@@ -284,7 +326,7 @@ export async function handleGroupSettingsCallback(
|
||||
}
|
||||
|
||||
// Handle reminder time adjustments (reminder1_add_1_hours, reminder2_sub_1_days, etc.)
|
||||
const reminderTimeMatch = action.match(/^reminder(\d)_(add|sub)_(\d+)_(minutes|hours|days)$/);
|
||||
const reminderTimeMatch = settingAction.match(/^reminder(\d)_(add|sub)_(\d+)_(minutes|hours|days)$/);
|
||||
if (reminderTimeMatch) {
|
||||
const slot = parseInt(reminderTimeMatch[1], 10) as 1 | 2 | 3;
|
||||
const operation = reminderTimeMatch[2] as 'add' | 'sub';
|
||||
@@ -317,9 +359,9 @@ export async function handleGroupSettingsCallback(
|
||||
newTime = { value: newMinutes, unit: 'minutes' };
|
||||
}
|
||||
|
||||
updatedSettings = await groupStateManager.updateReminderTime(chatId, slot, newTime);
|
||||
updatedSettings = await groupStateManager.updateReminderTime(groupId, slot, newTime);
|
||||
if (updatedSettings) {
|
||||
logUserAction(userId, 'Updated reminder time', { groupId: chatId, slot, newTime });
|
||||
logUserAction(userId, 'Updated reminder time', { groupId, slot, newTime });
|
||||
await bot.answerCallbackQuery(query.id, {
|
||||
text: `Reminder ${slot}: ${formatReminderTime(newTime)} before draw`
|
||||
});
|
||||
@@ -331,18 +373,18 @@ export async function handleGroupSettingsCallback(
|
||||
return;
|
||||
}
|
||||
|
||||
// Update the message with new settings
|
||||
// Update the message with new settings (in the DM)
|
||||
await bot.editMessageText(
|
||||
messages.groups.settingsOverview(updatedSettings),
|
||||
{
|
||||
chat_id: chatId,
|
||||
chat_id: dmChatId,
|
||||
message_id: messageId,
|
||||
parse_mode: 'Markdown',
|
||||
reply_markup: getGroupSettingsKeyboard(updatedSettings),
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
logger.error('Error in handleGroupSettingsCallback', { error, chatId, action });
|
||||
logger.error('Error in handleGroupSettingsCallback', { error, groupId, action });
|
||||
await bot.answerCallbackQuery(query.id, { text: 'Error updating settings' });
|
||||
}
|
||||
}
|
||||
@@ -371,14 +413,14 @@ function formatNewJackpotDelay(minutes: number): string {
|
||||
/**
|
||||
* Get time adjustment buttons for a reminder slot
|
||||
*/
|
||||
function getReminderTimeAdjustButtons(slot: number, currentTime: ReminderTime): TelegramBot.InlineKeyboardButton[] {
|
||||
function getReminderTimeAdjustButtons(slot: number, currentTime: ReminderTime, groupId: number): TelegramBot.InlineKeyboardButton[] {
|
||||
return [
|
||||
{ text: '−1m', callback_data: `group_reminder${slot}_sub_1_minutes` },
|
||||
{ text: '+1m', callback_data: `group_reminder${slot}_add_1_minutes` },
|
||||
{ text: '−1h', callback_data: `group_reminder${slot}_sub_1_hours` },
|
||||
{ text: '+1h', callback_data: `group_reminder${slot}_add_1_hours` },
|
||||
{ text: '−1d', callback_data: `group_reminder${slot}_sub_1_days` },
|
||||
{ text: '+1d', callback_data: `group_reminder${slot}_add_1_days` },
|
||||
{ text: '−1m', callback_data: `group_reminder${slot}_sub_1_minutes_${groupId}` },
|
||||
{ text: '+1m', callback_data: `group_reminder${slot}_add_1_minutes_${groupId}` },
|
||||
{ text: '−1h', callback_data: `group_reminder${slot}_sub_1_hours_${groupId}` },
|
||||
{ text: '+1h', callback_data: `group_reminder${slot}_add_1_hours_${groupId}` },
|
||||
{ text: '−1d', callback_data: `group_reminder${slot}_sub_1_days_${groupId}` },
|
||||
{ text: '+1d', callback_data: `group_reminder${slot}_add_1_days_${groupId}` },
|
||||
];
|
||||
}
|
||||
|
||||
@@ -394,19 +436,21 @@ function hasReminder(settings: GroupSettings, rt: ReminderTime): boolean {
|
||||
|
||||
/**
|
||||
* Generate inline keyboard for group settings
|
||||
* groupId is included in callback data so settings can be managed from DMs
|
||||
*/
|
||||
function getGroupSettingsKeyboard(settings: GroupSettings): TelegramBot.InlineKeyboardMarkup {
|
||||
const onOff = (val: boolean | undefined) => val !== false ? '✅' : '❌';
|
||||
const selected = (current: number, option: number) => current === option ? '●' : '○';
|
||||
const gid = settings.groupId; // Include group ID in all callbacks
|
||||
|
||||
const keyboard: TelegramBot.InlineKeyboardButton[][] = [
|
||||
[{
|
||||
text: `${onOff(settings.enabled)} Bot Enabled`,
|
||||
callback_data: 'group_toggle_enabled',
|
||||
callback_data: `group_toggle_enabled_${gid}`,
|
||||
}],
|
||||
[{
|
||||
text: `${onOff(settings.newJackpotAnnouncement)} New Jackpot Announcement`,
|
||||
callback_data: 'group_toggle_newjackpot',
|
||||
callback_data: `group_toggle_newjackpot_${gid}`,
|
||||
}],
|
||||
];
|
||||
|
||||
@@ -415,14 +459,14 @@ function getGroupSettingsKeyboard(settings: GroupSettings): TelegramBot.InlineKe
|
||||
keyboard.push(
|
||||
NEW_JACKPOT_DELAY_OPTIONS.map(minutes => ({
|
||||
text: `${selected(settings.newJackpotDelayMinutes ?? 5, minutes)} ${formatNewJackpotDelay(minutes)}`,
|
||||
callback_data: `group_newjackpot_delay_${minutes}`,
|
||||
callback_data: `group_newjackpot_delay_${minutes}_${gid}`,
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
keyboard.push([{
|
||||
text: `${onOff(settings.drawAnnouncements)} Draw Result Announcements`,
|
||||
callback_data: 'group_toggle_announcements',
|
||||
callback_data: `group_toggle_announcements_${gid}`,
|
||||
}]);
|
||||
|
||||
// Add announcement delay options if announcements are enabled
|
||||
@@ -430,14 +474,14 @@ function getGroupSettingsKeyboard(settings: GroupSettings): TelegramBot.InlineKe
|
||||
keyboard.push(
|
||||
ANNOUNCEMENT_DELAY_OPTIONS.map(seconds => ({
|
||||
text: `${selected(settings.announcementDelaySeconds || 0, seconds)} ${formatDelayOption(seconds)}`,
|
||||
callback_data: `group_announce_delay_${seconds}`,
|
||||
callback_data: `group_announce_delay_${seconds}_${gid}`,
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
keyboard.push([{
|
||||
text: `${onOff(settings.reminders)} Draw Reminders`,
|
||||
callback_data: 'group_toggle_reminders',
|
||||
callback_data: `group_toggle_reminders_${gid}`,
|
||||
}]);
|
||||
|
||||
// Add 3-tier reminder options if reminders are enabled
|
||||
@@ -455,39 +499,39 @@ function getGroupSettingsKeyboard(settings: GroupSettings): TelegramBot.InlineKe
|
||||
// Reminder 1
|
||||
keyboard.push([{
|
||||
text: `${onOff(r1Enabled)} Reminder 1: ${formatReminderTime(r1Time)} before`,
|
||||
callback_data: 'group_toggle_reminder1',
|
||||
callback_data: `group_toggle_reminder1_${gid}`,
|
||||
}]);
|
||||
if (r1Enabled) {
|
||||
keyboard.push(getReminderTimeAdjustButtons(1, r1Time));
|
||||
keyboard.push(getReminderTimeAdjustButtons(1, r1Time, gid));
|
||||
}
|
||||
|
||||
// Reminder 2
|
||||
keyboard.push([{
|
||||
text: `${onOff(r2Enabled)} Reminder 2: ${formatReminderTime(r2Time)} before`,
|
||||
callback_data: 'group_toggle_reminder2',
|
||||
callback_data: `group_toggle_reminder2_${gid}`,
|
||||
}]);
|
||||
if (r2Enabled) {
|
||||
keyboard.push(getReminderTimeAdjustButtons(2, r2Time));
|
||||
keyboard.push(getReminderTimeAdjustButtons(2, r2Time, gid));
|
||||
}
|
||||
|
||||
// Reminder 3
|
||||
keyboard.push([{
|
||||
text: `${onOff(r3Enabled)} Reminder 3: ${formatReminderTime(r3Time)} before`,
|
||||
callback_data: 'group_toggle_reminder3',
|
||||
callback_data: `group_toggle_reminder3_${gid}`,
|
||||
}]);
|
||||
if (r3Enabled) {
|
||||
keyboard.push(getReminderTimeAdjustButtons(3, r3Time));
|
||||
keyboard.push(getReminderTimeAdjustButtons(3, r3Time, gid));
|
||||
}
|
||||
}
|
||||
|
||||
keyboard.push(
|
||||
[{
|
||||
text: `${onOff(settings.ticketPurchaseAllowed)} Allow Ticket Purchases`,
|
||||
callback_data: 'group_toggle_purchases',
|
||||
callback_data: `group_toggle_purchases_${gid}`,
|
||||
}],
|
||||
[{
|
||||
text: '🔄 Refresh',
|
||||
callback_data: 'group_refresh',
|
||||
callback_data: `group_refresh_${gid}`,
|
||||
}]
|
||||
);
|
||||
|
||||
@@ -496,28 +540,30 @@ function getGroupSettingsKeyboard(settings: GroupSettings): TelegramBot.InlineKe
|
||||
|
||||
/**
|
||||
* Handle refresh callback
|
||||
* groupId is extracted from callback data since settings are managed in DMs
|
||||
*/
|
||||
export async function handleGroupRefresh(
|
||||
bot: TelegramBot,
|
||||
query: TelegramBot.CallbackQuery
|
||||
query: TelegramBot.CallbackQuery,
|
||||
groupId: number
|
||||
): Promise<void> {
|
||||
const chatId = query.message?.chat.id;
|
||||
const dmChatId = query.message?.chat.id;
|
||||
const messageId = query.message?.message_id;
|
||||
|
||||
if (!chatId || !messageId) return;
|
||||
if (!dmChatId || !messageId) return;
|
||||
|
||||
// Refresh auto-delete timer
|
||||
scheduleSettingsMessageDeletion(bot, chatId, messageId);
|
||||
scheduleSettingsMessageDeletion(bot, dmChatId, messageId);
|
||||
|
||||
await bot.answerCallbackQuery(query.id, { text: 'Refreshed!' });
|
||||
|
||||
const settings = await groupStateManager.getGroup(chatId);
|
||||
const settings = await groupStateManager.getGroup(groupId);
|
||||
if (!settings) return;
|
||||
|
||||
await bot.editMessageText(
|
||||
messages.groups.settingsOverview(settings),
|
||||
{
|
||||
chat_id: chatId,
|
||||
chat_id: dmChatId,
|
||||
message_id: messageId,
|
||||
parse_mode: 'Markdown',
|
||||
reply_markup: getGroupSettingsKeyboard(settings),
|
||||
|
||||
@@ -35,7 +35,7 @@ export async function handleSettingsCommand(
|
||||
|
||||
// Ensure notifications object exists
|
||||
const notifications = user.notifications || { ...DEFAULT_NOTIFICATIONS };
|
||||
const displayName = user.displayName || 'Anon';
|
||||
const displayName = stateManager.getDisplayName(user);
|
||||
|
||||
await bot.sendMessage(
|
||||
chatId,
|
||||
@@ -83,13 +83,14 @@ export async function handleSettingsCallback(
|
||||
});
|
||||
|
||||
// Update message
|
||||
const displayName = stateManager.getDisplayName(updatedUser);
|
||||
await bot.editMessageText(
|
||||
messages.settings.overview(updatedUser.displayName || 'Anon', updatedUser.notifications),
|
||||
messages.settings.overview(displayName, updatedUser.notifications),
|
||||
{
|
||||
chat_id: chatId,
|
||||
message_id: messageId,
|
||||
parse_mode: 'Markdown',
|
||||
reply_markup: getSettingsKeyboard(updatedUser.displayName || 'Anon', updatedUser.notifications),
|
||||
reply_markup: getSettingsKeyboard(displayName, updatedUser.notifications),
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -145,8 +146,8 @@ export async function handleDisplayNameInput(
|
||||
return;
|
||||
}
|
||||
|
||||
// Clean the display name
|
||||
const cleanName = text.replace(/[^\w\s\-_.]/g, '').trim() || 'Anon';
|
||||
// Clean the display name (allow @ for usernames)
|
||||
const cleanName = text.replace(/[^\w\s\-_.@]/g, '').trim() || 'Anon';
|
||||
|
||||
await stateManager.updateDisplayName(userId, cleanName);
|
||||
logUserAction(userId, 'Set display name', { displayName: cleanName });
|
||||
|
||||
@@ -340,57 +340,41 @@ bot.on('callback_query', async (query) => {
|
||||
logUserAction(query.from.id, 'Callback', { data });
|
||||
|
||||
try {
|
||||
// Handle group settings toggles
|
||||
// Handle group settings toggles (now includes group ID: group_toggle_enabled_-123456789)
|
||||
if (data.startsWith('group_toggle_')) {
|
||||
const action = data.replace('group_', '');
|
||||
await handleGroupSettingsCallback(bot, query, action);
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle group reminder time adjustment (reminder1_add_1_hours, etc.)
|
||||
if (data.match(/^group_reminder\d_(add|sub)_\d+_(minutes|hours|days)$/)) {
|
||||
// Handle group reminder time adjustment (reminder1_add_1_hours_-123456789, etc.)
|
||||
if (data.match(/^group_reminder\d_(add|sub)_\d+_(minutes|hours|days)_-?\d+$/)) {
|
||||
const action = data.replace('group_', '');
|
||||
await handleGroupSettingsCallback(bot, query, action);
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle group add reminder (legacy)
|
||||
if (data.startsWith('group_add_reminder_')) {
|
||||
// Handle group announcement delay selection (group_announce_delay_30_-123456789)
|
||||
if (data.match(/^group_announce_delay_\d+_-?\d+$/)) {
|
||||
const action = data.replace('group_', '');
|
||||
await handleGroupSettingsCallback(bot, query, action);
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle group remove reminder (legacy)
|
||||
if (data.startsWith('group_remove_reminder_')) {
|
||||
// Handle new jackpot announcement delay selection (group_newjackpot_delay_5_-123456789)
|
||||
if (data.match(/^group_newjackpot_delay_\d+_-?\d+$/)) {
|
||||
const action = data.replace('group_', '');
|
||||
await handleGroupSettingsCallback(bot, query, action);
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle group clear reminders (legacy)
|
||||
if (data === 'group_clear_reminders') {
|
||||
await handleGroupSettingsCallback(bot, query, 'clear_reminders');
|
||||
return;
|
||||
// Handle group refresh (group_refresh_-123456789)
|
||||
if (data.match(/^group_refresh_-?\d+$/)) {
|
||||
const groupIdMatch = data.match(/^group_refresh_(-?\d+)$/);
|
||||
if (groupIdMatch) {
|
||||
const groupId = parseInt(groupIdMatch[1], 10);
|
||||
await handleGroupRefresh(bot, query, groupId);
|
||||
}
|
||||
|
||||
// Handle group announcement delay selection
|
||||
if (data.startsWith('group_announce_delay_')) {
|
||||
const action = data.replace('group_', '');
|
||||
await handleGroupSettingsCallback(bot, query, action);
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle new jackpot announcement delay selection
|
||||
if (data.startsWith('group_newjackpot_delay_')) {
|
||||
const action = data.replace('group_', '');
|
||||
await handleGroupSettingsCallback(bot, query, action);
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle group refresh
|
||||
if (data === 'group_refresh') {
|
||||
await handleGroupRefresh(bot, query);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -549,7 +549,7 @@ Be first to enter! Use /buyticket to buy tickets! 🍀`;
|
||||
`⚙️ *Your Settings*
|
||||
|
||||
👤 *Display Name:* ${displayName}
|
||||
_(Used when announcing winners)_
|
||||
_(Shown when announcing winners. Defaults to your @username)_
|
||||
|
||||
*Notifications:*
|
||||
${notifications.drawReminders ? '✅' : '❌'} Draw Reminders _(15 min before draws)_
|
||||
@@ -563,6 +563,7 @@ Tap buttons below to change settings:`,
|
||||
Enter your display name (max 20 characters).
|
||||
This name will be shown if you win!
|
||||
|
||||
_Your Telegram @username is used by default._
|
||||
_Send "Anon" to stay anonymous._`,
|
||||
|
||||
nameTooLong: '❌ Display name must be 20 characters or less. Please try again:',
|
||||
|
||||
@@ -165,6 +165,7 @@ class BotDatabase {
|
||||
|
||||
/**
|
||||
* Create a new user
|
||||
* Default display name is @username if available, otherwise 'Anon'
|
||||
*/
|
||||
createUser(
|
||||
telegramId: number,
|
||||
@@ -175,13 +176,15 @@ class BotDatabase {
|
||||
if (!this.db) throw new Error('Database not initialized');
|
||||
|
||||
const now = new Date().toISOString();
|
||||
// Default display name: @username if available, otherwise 'Anon'
|
||||
const defaultDisplayName = username ? `@${username}` : 'Anon';
|
||||
|
||||
this.db.prepare(`
|
||||
INSERT INTO users (telegram_id, username, first_name, last_name, display_name, created_at, updated_at)
|
||||
VALUES (?, ?, ?, ?, 'Anon', ?, ?)
|
||||
`).run(telegramId, username || null, firstName || null, lastName || null, now, now);
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
`).run(telegramId, username || null, firstName || null, lastName || null, defaultDisplayName, now, now);
|
||||
|
||||
logger.info('New user created', { telegramId, username });
|
||||
logger.info('New user created', { telegramId, username, displayName: defaultDisplayName });
|
||||
|
||||
return this.getUser(telegramId)!;
|
||||
}
|
||||
|
||||
@@ -173,7 +173,7 @@ class NotificationScheduler {
|
||||
if (status.result.is_winner) {
|
||||
const user = await stateManager.getUser(telegramId);
|
||||
winnerTelegramId = telegramId;
|
||||
winnerDisplayName = user?.displayName || 'Anon';
|
||||
winnerDisplayName = user ? stateManager.getDisplayName(user) : 'Anon';
|
||||
|
||||
const winningTicket = status.tickets.find(t => t.is_winning_ticket);
|
||||
if (winningTicket) {
|
||||
@@ -396,7 +396,7 @@ class NotificationScheduler {
|
||||
if (status.result.is_winner) {
|
||||
const user = await stateManager.getUser(telegramId);
|
||||
winnerTelegramId = telegramId;
|
||||
winnerDisplayName = user?.displayName || 'Anon';
|
||||
winnerDisplayName = user ? stateManager.getDisplayName(user) : 'Anon';
|
||||
|
||||
const winningTicket = status.tickets.find(t => t.is_winning_ticket);
|
||||
if (winningTicket) {
|
||||
|
||||
@@ -170,9 +170,17 @@ class StateManager {
|
||||
|
||||
/**
|
||||
* Get user's display name (for announcements)
|
||||
* Priority: displayName > @username > 'Anon'
|
||||
*/
|
||||
getDisplayName(user: TelegramUser): string {
|
||||
return user.displayName || 'Anon';
|
||||
if (user.displayName && user.displayName !== 'Anon') {
|
||||
return user.displayName;
|
||||
}
|
||||
// Fall back to @username if available
|
||||
if (user.username) {
|
||||
return `@${user.username}`;
|
||||
}
|
||||
return 'Anon';
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user