Files
LightningLotto/telegram_bot/src/handlers/settings.ts
Michilis 00f09236a3 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()
2025-12-12 15:28:05 +00:00

174 lines
5.0 KiB
TypeScript

import TelegramBot from 'node-telegram-bot-api';
import { stateManager } from '../services/state';
import { logger, logUserAction } from '../services/logger';
import { messages } from '../messages';
import { getMainMenuKeyboard, getSettingsKeyboard } from '../utils/keyboards';
import { NotificationPreferences, DEFAULT_NOTIFICATIONS } from '../types';
/**
* Handle /settings command (private chat only)
*/
export async function handleSettingsCommand(
bot: TelegramBot,
msg: TelegramBot.Message
): Promise<void> {
const chatId = msg.chat.id;
const userId = msg.from?.id;
if (!userId) return;
// Only works in private chats
if (msg.chat.type !== 'private') {
await bot.sendMessage(chatId, '❌ This command only works in private chat. Message me directly!');
return;
}
logUserAction(userId, 'Viewed settings');
const user = await stateManager.getUser(userId);
if (!user) {
await bot.sendMessage(chatId, messages.errors.startFirst, {
reply_markup: getMainMenuKeyboard(),
});
return;
}
// Ensure notifications object exists
const notifications = user.notifications || { ...DEFAULT_NOTIFICATIONS };
const displayName = stateManager.getDisplayName(user);
await bot.sendMessage(
chatId,
messages.settings.overview(displayName, notifications),
{
parse_mode: 'Markdown',
reply_markup: getSettingsKeyboard(displayName, notifications),
}
);
}
/**
* Handle settings callback
*/
export async function handleSettingsCallback(
bot: TelegramBot,
query: TelegramBot.CallbackQuery,
action: string
): Promise<void> {
const chatId = query.message?.chat.id;
const userId = query.from.id;
const messageId = query.message?.message_id;
if (!chatId || !messageId) return;
const user = await stateManager.getUser(userId);
if (!user) {
await bot.answerCallbackQuery(query.id, { text: 'Please /start first' });
return;
}
try {
// Handle notification toggles
if (action.startsWith('toggle_notif_')) {
const setting = action.replace('toggle_notif_', '') as keyof NotificationPreferences;
const currentNotifications = user.notifications || { ...DEFAULT_NOTIFICATIONS };
const newValue = !currentNotifications[setting];
const updatedUser = await stateManager.updateNotifications(userId, { [setting]: newValue });
if (updatedUser) {
logUserAction(userId, 'Updated notification setting', { setting, newValue });
await bot.answerCallbackQuery(query.id, {
text: `${setting} ${newValue ? 'enabled' : 'disabled'}`,
});
// Update message
const displayName = stateManager.getDisplayName(updatedUser);
await bot.editMessageText(
messages.settings.overview(displayName, updatedUser.notifications),
{
chat_id: chatId,
message_id: messageId,
parse_mode: 'Markdown',
reply_markup: getSettingsKeyboard(displayName, updatedUser.notifications),
}
);
}
return;
}
// Handle display name change
if (action === 'change_name') {
await bot.answerCallbackQuery(query.id);
await stateManager.updateUserState(userId, 'awaiting_display_name');
await bot.sendMessage(
chatId,
messages.settings.enterDisplayName,
{ parse_mode: 'Markdown' }
);
return;
}
// Handle back to menu
if (action === 'back_menu') {
await bot.answerCallbackQuery(query.id);
await bot.deleteMessage(chatId, messageId);
await bot.sendMessage(chatId, messages.menu.header, {
parse_mode: 'Markdown',
reply_markup: getMainMenuKeyboard(),
});
return;
}
await bot.answerCallbackQuery(query.id);
} catch (error) {
logger.error('Error in handleSettingsCallback', { error, userId, action });
await bot.answerCallbackQuery(query.id, { text: 'Error updating settings' });
}
}
/**
* Handle display name input
*/
export async function handleDisplayNameInput(
bot: TelegramBot,
msg: TelegramBot.Message
): Promise<void> {
const chatId = msg.chat.id;
const userId = msg.from?.id;
const text = msg.text?.trim();
if (!userId || !text) return;
// Validate display name (max 20 chars, alphanumeric + spaces + some symbols)
if (text.length > 20) {
await bot.sendMessage(chatId, messages.settings.nameTooLong);
return;
}
// 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 });
const user = await stateManager.getUser(userId);
const notifications = user?.notifications || { ...DEFAULT_NOTIFICATIONS };
await bot.sendMessage(
chatId,
messages.settings.nameUpdated(cleanName),
{
parse_mode: 'Markdown',
reply_markup: getSettingsKeyboard(cleanName, notifications),
}
);
}
export default {
handleSettingsCommand,
handleSettingsCallback,
handleDisplayNameInput,
};