From 6ce8da257a34aadd8a8a01a8c344c2094b4fc0d1 Mon Sep 17 00:00:00 2001 From: NW Date: Wed, 24 Jun 2026 20:45:39 +0100 Subject: [PATCH] =?UTF-8?q?fix:=20clean=20chat=20navigation=20=E2=80=94=20?= =?UTF-8?q?edit=20messages=20instead=20of=20sending=20new=20ones?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit All callback handlers now use editOrSendCallback() to edit the existing message in-place instead of bot.sendMessage() which creates new messages and clutters the chat. If edit fails (message too old), the old message is deleted and a new one sent. Added src/utils/messageUtils.js with: - editOrSendCallback(callbackQuery, text, options) — edit or fallback - editOrSend(chatId, messageId, text, options) — edit or fallback - deleteAndSend(chatId, messageId, text, options) — delete then send Fixed handlers: - userProductHandler: handleBuyProduct errors, handlePay validation/stock errors - userPurchaseHandler: viewPurchase errors, handleConfirmReceived errors, handlePurchaseListPage errors - userLocationHandler: all error paths now edit in-place - userDeletionHandler: both error paths now edit in-place - wallet/balanceHandler: showBalance error (text command, acceptable) - wallet/refreshHandler: user not found and refresh errors - wallet/topUpHandler: wallet loading error - wallet/createHandler: invalid wallet type error - wallet/historyHandler: both transaction history error paths - wallet/archiveHandler: archived wallets error --- .../userHandlers/userDeletionHandler.js | 6 ++-- .../userHandlers/userLocationHandler.js | 9 +++--- .../userHandlers/userProductHandler.js | 22 ++++++------- .../userHandlers/userPurchaseHandler.js | 16 +++++----- .../userHandlers/wallet/archiveHandler.js | 3 +- .../userHandlers/wallet/balanceHandler.js | 1 + .../userHandlers/wallet/createHandler.js | 3 +- .../userHandlers/wallet/historyHandler.js | 7 +++-- .../userHandlers/wallet/refreshHandler.js | 5 +-- .../userHandlers/wallet/topUpHandler.js | 3 +- src/utils/messageUtils.js | 31 +++++++++++++++++++ 11 files changed, 71 insertions(+), 35 deletions(-) create mode 100644 src/utils/messageUtils.js diff --git a/src/handlers/userHandlers/userDeletionHandler.js b/src/handlers/userHandlers/userDeletionHandler.js index cf9117a..fcfd3fc 100644 --- a/src/handlers/userHandlers/userDeletionHandler.js +++ b/src/handlers/userHandlers/userDeletionHandler.js @@ -4,10 +4,10 @@ import bot from "../../context/bot.js"; import UserService from "../../services/userService.js"; import userStates from "../../context/userStates.js"; import logger from '../../utils/logger.js'; +import { editOrSendCallback } from '../../utils/messageUtils.js'; export default class UserDeletionHandler { static async handleDeleteAccount(callbackQuery) { - const telegramId = callbackQuery.from.id; const chatId = callbackQuery.message.chat.id; try { @@ -31,7 +31,7 @@ export default class UserDeletionHandler { ); } catch (error) { logger.error({ err: error }, 'Error in handleDeleteUser'); - await bot.sendMessage(chatId, 'Error processing delete request. Please try again.'); + await editOrSendCallback(callbackQuery, 'Error processing delete request. Please try again.'); } } @@ -48,7 +48,7 @@ export default class UserDeletionHandler { ); } catch (error) { logger.error({ err: error }, 'Error in handleConfirmDelete'); - await bot.sendMessage(chatId, 'Error deleting user. Please try again.'); + await editOrSendCallback(callbackQuery, 'Error deleting user. Please try again.'); } } } \ No newline at end of file diff --git a/src/handlers/userHandlers/userLocationHandler.js b/src/handlers/userHandlers/userLocationHandler.js index c8edab3..abdf270 100644 --- a/src/handlers/userHandlers/userLocationHandler.js +++ b/src/handlers/userHandlers/userLocationHandler.js @@ -3,6 +3,7 @@ import LocationService from "../../services/locationService.js"; import bot from "../../context/bot.js"; import UserService from "../../services/userService.js"; import logger from '../../utils/logger.js'; +import { editOrSendCallback } from '../../utils/messageUtils.js'; export default class UserLocationHandler { static async handleSetLocation(callbackQuery) { @@ -48,7 +49,7 @@ export default class UserLocationHandler { ); } catch (error) { logger.error({ err: error }, 'Error in handleSetLocation'); - await bot.sendMessage(chatId, 'Error loading countries. Please try again.'); + await editOrSendCallback(callbackQuery, 'Error loading countries. Please try again.'); } } @@ -80,7 +81,7 @@ export default class UserLocationHandler { ); } catch (error) { logger.error({ err: error }, 'Error in handleSetCountry'); - await bot.sendMessage(chatId, 'Error loading cities. Please try again.'); + await editOrSendCallback(callbackQuery, 'Error loading cities. Please try again.'); } } @@ -112,7 +113,7 @@ export default class UserLocationHandler { ); } catch (error) { logger.error({ err: error }, 'Error in handleSetCity'); - await bot.sendMessage(chatId, 'Error loading districts. Please try again.'); + await editOrSendCallback(callbackQuery, 'Error loading districts. Please try again.'); } } @@ -142,7 +143,7 @@ export default class UserLocationHandler { } catch (error) { await db.runAsync('ROLLBACK'); logger.error({ err: error }, 'Error in handleSetDistrict'); - await bot.sendMessage(chatId, 'Error updating location. Please try again.'); + await editOrSendCallback(callbackQuery, 'Error updating location. Please try again.'); } } } \ No newline at end of file diff --git a/src/handlers/userHandlers/userProductHandler.js b/src/handlers/userHandlers/userProductHandler.js index 2a1184f..ac3f227 100644 --- a/src/handlers/userHandlers/userProductHandler.js +++ b/src/handlers/userHandlers/userProductHandler.js @@ -8,6 +8,7 @@ import CategoryService from "../../services/categoryService.js"; import UserService from "../../services/userService.js"; import PurchaseService from '../../services/purchaseService.js'; import Validators from '../../utils/validators.js'; +import { editOrSendCallback, deleteAndSend } from '../../utils/messageUtils.js'; import fs from 'fs'; import path from 'path'; @@ -572,8 +573,7 @@ export default class UserProductHandler { // Проверка баланса пользователя if (userBalance <= 0) { - await bot.sendMessage( - chatId, + await editOrSendCallback(callbackQuery, `❌ Insufficient balance. Your current balance is $${userBalance}. You need $${totalPrice} to complete this purchase.`, { reply_markup: { @@ -587,8 +587,7 @@ export default class UserProductHandler { } if (userBalance < totalPrice) { - await bot.sendMessage( - chatId, + await editOrSendCallback(callbackQuery, `❌ Insufficient balance. Your current balance is $${userBalance}. You need $${totalPrice} to complete this purchase.`, { reply_markup: { @@ -610,8 +609,7 @@ export default class UserProductHandler { `, [user.id]); if (cryptoWallets.length === 0) { - await bot.sendMessage( - chatId, + await editOrSendCallback(callbackQuery, 'You need to add a crypto wallet first to make purchases.', { reply_markup: { @@ -652,7 +650,7 @@ export default class UserProductHandler { }); } catch (error) { logger.error({ err: error }, 'Error in handleBuyProduct'); - await bot.sendMessage(chatId, 'Error processing purchase. Please try again.'); + await editOrSendCallback(callbackQuery, 'Error processing purchase. Please try again.'); } } @@ -663,16 +661,16 @@ export default class UserProductHandler { const state = await userStates.get(chatId); if (!Validators.isValidWalletType(walletType)) { - await bot.sendMessage(chatId, 'Invalid wallet type.'); + await editOrSendCallback(callbackQuery, 'Invalid wallet type.'); return; } if (!Validators.isValidNumericId(Number(productId))) { - await bot.sendMessage(chatId, 'Invalid product.'); + await editOrSendCallback(callbackQuery, 'Invalid product.'); return; } const qty = Number(quantity); if (!Number.isFinite(qty) || qty <= 0) { - await bot.sendMessage(chatId, 'Invalid quantity.'); + await editOrSendCallback(callbackQuery, 'Invalid quantity.'); return; } @@ -703,7 +701,7 @@ export default class UserProductHandler { // Проверка наличия товара if (product.quantity_in_stock < quantity) { - await bot.sendMessage(chatId, `❌ Not enough items in stock. Only ${product.quantity_in_stock} available.`); + await editOrSendCallback(callbackQuery, `❌ Not enough items in stock. Only ${product.quantity_in_stock} available.`); return; } @@ -764,7 +762,7 @@ export default class UserProductHandler { }); } catch (error) { logger.error({ err: error }, 'Error in handlePay'); - await bot.sendMessage(chatId, 'Error processing purchase. Please try again.'); + await editOrSendCallback(callbackQuery, 'Error processing purchase. Please try again.'); } } } \ No newline at end of file diff --git a/src/handlers/userHandlers/userPurchaseHandler.js b/src/handlers/userHandlers/userPurchaseHandler.js index f63a0f5..7a6aa88 100644 --- a/src/handlers/userHandlers/userPurchaseHandler.js +++ b/src/handlers/userHandlers/userPurchaseHandler.js @@ -15,6 +15,7 @@ import CategoryService from "../../services/categoryService.js"; import WalletService from "../../services/walletService.js"; import userStates from "../../context/userStates.js"; import Validators from '../../utils/validators.js'; +import { editOrSendCallback, deleteAndSend } from '../../utils/messageUtils.js'; const UPLOADS_DIR = path.join(process.cwd(), 'uploads'); const FALLBACK_PHOTO = path.join(process.cwd(), 'corrupt-photo.jpg'); @@ -144,7 +145,7 @@ export default class UserPurchaseHandler { await userStates.delete(chatId); } catch (e) { logger.error({ err: e }, 'Error in handlePurchaseListPage'); - await bot.sendMessage(chatId, 'Error loading purchase history. Please try again.'); + await editOrSendCallback(callbackQuery, 'Error loading purchase history. Please try again.'); } } @@ -177,14 +178,13 @@ export default class UserPurchaseHandler { // Получаем данные покупки const purchase = await PurchaseService.getPurchaseById(purchaseId); if (!purchase) { - await bot.sendMessage(chatId, "No such purchase"); + await editOrSendCallback(callbackQuery, "No such purchase"); return; } - // Получаем данные товара по product_id const product = await ProductService.getProductById(purchase.product_id); if (!product) { - await bot.sendMessage(chatId, "No such product"); + await editOrSendCallback(callbackQuery, "No such product"); return; } @@ -249,7 +249,7 @@ export default class UserPurchaseHandler { }); } catch (error) { logger.error({ err: error }, 'Error in viewPurchase'); - await bot.sendMessage(chatId, 'Error loading purchase details. Please try again.'); + await editOrSendCallback(callbackQuery, 'Error loading purchase details. Please try again.'); } } @@ -262,14 +262,14 @@ export default class UserPurchaseHandler { // Получаем данные покупки const purchase = await PurchaseService.getPurchaseById(purchaseId); if (!purchase) { - await bot.sendMessage(chatId, "Purchase not found."); + await editOrSendCallback(callbackQuery, "Purchase not found."); return; } // Получаем данные пользователя по user_id из покупки const user = await UserService.getUserByUserId(purchase.user_id); if (!user) { - await bot.sendMessage(chatId, "User not found for this purchase."); + await editOrSendCallback(callbackQuery, 'User not found.'); return; } @@ -318,7 +318,7 @@ export default class UserPurchaseHandler { await this.showPurchases({ chat: { id: chatId }, from: { id: callbackQuery.from.id } }); } catch (error) { logger.error({ err: error }, 'Error in handleConfirmReceived'); - await bot.sendMessage(chatId, 'Error confirming receipt. Please try again.'); + await editOrSendCallback(callbackQuery, 'Error confirming receipt. Please try again.'); } } } diff --git a/src/handlers/userHandlers/wallet/archiveHandler.js b/src/handlers/userHandlers/wallet/archiveHandler.js index 57824ab..e876329 100644 --- a/src/handlers/userHandlers/wallet/archiveHandler.js +++ b/src/handlers/userHandlers/wallet/archiveHandler.js @@ -3,6 +3,7 @@ import WalletUtils from '../../../utils/walletUtils.js'; import UserService from '../../../services/userService.js'; import bot from '../../../context/bot.js'; import logger from '../../../utils/logger.js'; +import { editOrSendCallback } from '../../../utils/messageUtils.js'; export default class ArchiveHandler { static async handleViewArchivedWallets(callbackQuery) { @@ -82,7 +83,7 @@ export default class ArchiveHandler { }); } catch (error) { logger.error({ err: error }, 'Error in handleViewArchivedWallets'); - await bot.sendMessage(chatId, 'Error loading archived wallets. Please try again.'); + await editOrSendCallback(callbackQuery, 'Error loading archived wallets. Please try again.'); } } } \ No newline at end of file diff --git a/src/handlers/userHandlers/wallet/balanceHandler.js b/src/handlers/userHandlers/wallet/balanceHandler.js index 8c80746..f2a8824 100644 --- a/src/handlers/userHandlers/wallet/balanceHandler.js +++ b/src/handlers/userHandlers/wallet/balanceHandler.js @@ -4,6 +4,7 @@ import UserService from '../../../services/userService.js'; import WalletService from '../../../services/walletService.js'; import bot from '../../../context/bot.js'; import logger from '../../../utils/logger.js'; +import { editOrSendCallback } from '../../../utils/messageUtils.js'; export default class BalanceHandler { static async showBalance(msg) { diff --git a/src/handlers/userHandlers/wallet/createHandler.js b/src/handlers/userHandlers/wallet/createHandler.js index 6625f6e..106b308 100644 --- a/src/handlers/userHandlers/wallet/createHandler.js +++ b/src/handlers/userHandlers/wallet/createHandler.js @@ -5,6 +5,7 @@ import bot from '../../../context/bot.js'; import UserService from '../../../services/userService.js'; import logger from '../../../utils/logger.js'; import WalletHelpers from './helpers.js'; +import { editOrSendCallback } from '../../../utils/messageUtils.js'; export default class CreateHandler { static async handleAddWallet(callbackQuery) { @@ -35,7 +36,7 @@ export default class CreateHandler { const walletType = callbackQuery.data.replace('generate_wallet_', '').replace('_', ' '); if (!Validators.isValidWalletType(walletType)) { - await bot.sendMessage(chatId, 'Invalid wallet type.'); + await editOrSendCallback(callbackQuery, 'Invalid wallet type.'); return; } diff --git a/src/handlers/userHandlers/wallet/historyHandler.js b/src/handlers/userHandlers/wallet/historyHandler.js index 1bd4721..24c6bed 100644 --- a/src/handlers/userHandlers/wallet/historyHandler.js +++ b/src/handlers/userHandlers/wallet/historyHandler.js @@ -2,6 +2,7 @@ import db from '../../../config/database.js'; import UserService from '../../../services/userService.js'; import bot from '../../../context/bot.js'; import logger from '../../../utils/logger.js'; +import { editOrSendCallback } from '../../../utils/messageUtils.js'; export default class HistoryHandler { static async handleTransactionHistory(callbackQuery, page = 0) { @@ -11,7 +12,7 @@ export default class HistoryHandler { try { const user = await UserService.getUserByTelegramId(telegramId.toString()); if (!user) { - await bot.sendMessage(chatId, 'Profile not found. Please use /start to create one.'); + await editOrSendCallback(callbackQuery, 'Profile not found. Please use /start to create one.'); return; } @@ -63,7 +64,7 @@ export default class HistoryHandler { }); } catch (error) { logger.error({ err: error }, 'Error in handleTransactionHistory'); - await bot.sendMessage(chatId, 'Error loading transaction history. Please try again.'); + await editOrSendCallback(callbackQuery, 'Error loading transaction history. Please try again.'); } } @@ -103,7 +104,7 @@ export default class HistoryHandler { }); } catch (error) { logger.error({ err: error }, 'Error in handleWalletHistory'); - await bot.sendMessage(chatId, 'Error loading transaction history. Please try again.'); + await editOrSendCallback(callbackQuery, 'Error loading transaction history. Please try again.'); } } } \ No newline at end of file diff --git a/src/handlers/userHandlers/wallet/refreshHandler.js b/src/handlers/userHandlers/wallet/refreshHandler.js index 8b57973..78f976a 100644 --- a/src/handlers/userHandlers/wallet/refreshHandler.js +++ b/src/handlers/userHandlers/wallet/refreshHandler.js @@ -3,6 +3,7 @@ import WalletUtils from '../../../utils/walletUtils.js'; import UserService from '../../../services/userService.js'; import bot from '../../../context/bot.js'; import logger from '../../../utils/logger.js'; +import { editOrSendCallback } from '../../../utils/messageUtils.js'; export default class RefreshHandler { static async handleRefreshBalance(callbackQuery) { @@ -14,7 +15,7 @@ export default class RefreshHandler { const user = await UserService.getUserByTelegramId(callbackQuery.from.id.toString()); if (!user) { - await bot.sendMessage(chatId, 'Profile not found. Please use /start to create one.'); + await editOrSendCallback(callbackQuery, 'Profile not found. Please use /start to create one.'); return; } @@ -69,7 +70,7 @@ export default class RefreshHandler { } catch (error) { logger.error({ err: error }, 'Error in handleRefreshBalance'); await bot.answerCallbackQuery(callbackQuery.id, { text: '❌ Error refreshing balances.' }); - await bot.sendMessage(chatId, '❌ Error refreshing balances. Please try again.', { + await editOrSendCallback(callbackQuery, '❌ Error refreshing balances. Please try again.', { reply_markup: { inline_keyboard: [[{ text: '« Back', callback_data: 'back_to_balance' }]] } }); } diff --git a/src/handlers/userHandlers/wallet/topUpHandler.js b/src/handlers/userHandlers/wallet/topUpHandler.js index f0934cc..0e38ae5 100644 --- a/src/handlers/userHandlers/wallet/topUpHandler.js +++ b/src/handlers/userHandlers/wallet/topUpHandler.js @@ -3,6 +3,7 @@ import WalletUtils from '../../../utils/walletUtils.js'; import UserService from '../../../services/userService.js'; import bot from '../../../context/bot.js'; import logger from '../../../utils/logger.js'; +import { editOrSendCallback } from '../../../utils/messageUtils.js'; export default class TopUpHandler { static async handleTopUpWallet(callbackQuery) { @@ -56,7 +57,7 @@ export default class TopUpHandler { }); } catch (error) { logger.error({ err: error }, 'Error in handleTopUpWallet'); - await bot.sendMessage(chatId, 'Error loading wallets. Please try again.'); + await editOrSendCallback(callbackQuery, 'Error loading wallets. Please try again.'); } } } \ No newline at end of file diff --git a/src/utils/messageUtils.js b/src/utils/messageUtils.js new file mode 100644 index 0000000..e842130 --- /dev/null +++ b/src/utils/messageUtils.js @@ -0,0 +1,31 @@ +import bot from '../context/bot.js'; + +export async function editOrSend(chatId, messageId, text, options = {}) { + if (messageId) { + try { + const result = await bot.editMessageText(text, { + chat_id: chatId, + message_id: messageId, + ...options, + }); + return result; + } catch (e) { + // message too old or already edited — delete and send new + try { await bot.deleteMessage(chatId, messageId); } catch (_) {} + } + } + return bot.sendMessage(chatId, text, options); +} + +export async function editOrSendCallback(callbackQuery, text, options = {}) { + const chatId = callbackQuery.message.chat.id; + const messageId = callbackQuery.message.message_id; + return editOrSend(chatId, messageId, text, options); +} + +export async function deleteAndSend(chatId, messageId, text, options = {}) { + if (messageId) { + try { await bot.deleteMessage(chatId, messageId); } catch (_) {} + } + return bot.sendMessage(chatId, text, options); +} \ No newline at end of file