Add SQLite database for Telegram bot user/group settings
- Replace Redis/in-memory storage with SQLite for persistence - Add database.ts service with tables for users, groups, purchases, participants - Update state.ts and groupState.ts to use SQLite backend - Fix buyer_name to use display name instead of Telegram ID - Remove legacy reminder array handlers (now using 3-slot system) - Add better-sqlite3 dependency, remove ioredis - Update env.example with BOT_DATABASE_PATH option - Add data/ directory to .gitignore for database files
This commit is contained in:
172
telegram_bot/src/handlers/settings.ts
Normal file
172
telegram_bot/src/handlers/settings.ts
Normal file
@@ -0,0 +1,172 @@
|
||||
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 = user.displayName || 'Anon';
|
||||
|
||||
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
|
||||
await bot.editMessageText(
|
||||
messages.settings.overview(updatedUser.displayName || 'Anon', updatedUser.notifications),
|
||||
{
|
||||
chat_id: chatId,
|
||||
message_id: messageId,
|
||||
parse_mode: 'Markdown',
|
||||
reply_markup: getSettingsKeyboard(updatedUser.displayName || 'Anon', 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
|
||||
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,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user