fix: clean chat navigation — edit messages instead of sending new ones
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
This commit is contained in:
@@ -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.');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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.');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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' }]] }
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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.');
|
||||
}
|
||||
}
|
||||
}
|
||||
31
src/utils/messageUtils.js
Normal file
31
src/utils/messageUtils.js
Normal file
@@ -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);
|
||||
}
|
||||
Reference in New Issue
Block a user