From e56f1194e9389ea9956bda36fb1b35eac3dc54e8 Mon Sep 17 00:00:00 2001 From: whekin Date: Thu, 12 Mar 2026 02:54:42 +0400 Subject: [PATCH] fix(bot): improve utilities reminder template flow --- apps/bot/src/i18n/locales/en.ts | 3 +- apps/bot/src/i18n/locales/ru.ts | 2 +- apps/bot/src/reminder-topic-utilities.test.ts | 31 ++++++++++- apps/bot/src/reminder-topic-utilities.ts | 52 +++++++++++++------ 4 files changed, 69 insertions(+), 19 deletions(-) diff --git a/apps/bot/src/i18n/locales/en.ts b/apps/bot/src/i18n/locales/en.ts index 47a5775..cbd64dc 100644 --- a/apps/bot/src/i18n/locales/en.ts +++ b/apps/bot/src/i18n/locales/en.ts @@ -270,7 +270,8 @@ export const enBotTranslations: BotTranslationCatalog = { `I could not read that amount for ${categoryName}. Reply with a number in ${currency}, or send 0 / "skip".`, templateIntro: (currency) => `Fill in the utility amounts below in ${currency}, then send the completed message back in this topic.`, - templateInstruction: 'Use 0 or skip for any category you want to leave empty.', + templateInstruction: + 'For any category you do not want to add, leave it blank, remove the line entirely, or send 0 / "skip".', templateInvalid: 'I could not read any utility amounts from that template. Send the filled template back with at least one amount.', summaryTitle: (period) => `Utility charges for ${period}`, diff --git a/apps/bot/src/i18n/locales/ru.ts b/apps/bot/src/i18n/locales/ru.ts index 8a03e0b..8def867 100644 --- a/apps/bot/src/i18n/locales/ru.ts +++ b/apps/bot/src/i18n/locales/ru.ts @@ -275,7 +275,7 @@ export const ruBotTranslations: BotTranslationCatalog = { templateIntro: (currency) => `Заполните суммы по коммуналке ниже в ${currency}, затем отправьте заполненное сообщение обратно в этот топик.`, templateInstruction: - 'Для любой категории, которую не нужно добавлять, укажите 0 или слово «пропуск».', + 'Для любой категории, которую не нужно добавлять, оставьте поле пустым, удалите строку целиком или укажите 0 / «пропуск».', templateInvalid: 'Не удалось распознать ни одной суммы в этом шаблоне. Отправьте заполненный шаблон хотя бы с одной суммой.', summaryTitle: (period) => `Коммунальные начисления за ${period}`, diff --git a/apps/bot/src/reminder-topic-utilities.test.ts b/apps/bot/src/reminder-topic-utilities.test.ts index 0fa7c22..9416d86 100644 --- a/apps/bot/src/reminder-topic-utilities.test.ts +++ b/apps/bot/src/reminder-topic-utilities.test.ts @@ -375,7 +375,8 @@ describe('registerReminderTopicUtilities', () => { expect(calls[1]).toMatchObject({ method: 'sendMessage', payload: { - text: expect.stringContaining('Electricity:'), + text: expect.stringContaining('
Electricity: \nWater: 
'), + parse_mode: 'HTML', message_thread_id: 555 } }) @@ -391,6 +392,34 @@ describe('registerReminderTopicUtilities', () => { }) }) + test('treats blank or removed template lines as skipped categories', async () => { + const { bot, calls } = setupBot() + + await bot.handleUpdate(reminderCallbackUpdate(REMINDER_UTILITY_TEMPLATE_CALLBACK) as never) + + calls.length = 0 + await bot.handleUpdate(reminderMessageUpdate('Electricity: 22\nWater: ') as never) + + expect(calls[0]).toMatchObject({ + method: 'sendMessage', + payload: { + text: expect.stringContaining('- Electricity: 22.00 GEL') + } + }) + + calls.length = 0 + await bot.handleUpdate(reminderCallbackUpdate(REMINDER_UTILITY_TEMPLATE_CALLBACK) as never) + calls.length = 0 + await bot.handleUpdate(reminderMessageUpdate('Electricity: 22') as never) + + expect(calls[0]).toMatchObject({ + method: 'sendMessage', + payload: { + text: expect.stringContaining('- Electricity: 22.00 GEL') + } + }) + }) + test('treats expired pending reminder submissions as unavailable', async () => { const { bot, calls, promptRepository } = setupBot() diff --git a/apps/bot/src/reminder-topic-utilities.ts b/apps/bot/src/reminder-topic-utilities.ts index 061b520..3f5696f 100644 --- a/apps/bot/src/reminder-topic-utilities.ts +++ b/apps/bot/src/reminder-topic-utilities.ts @@ -182,20 +182,32 @@ function parseTemplateEntries( return entries.length > 0 ? entries : null } +function escapeHtml(raw: string): string { + return raw.replaceAll('&', '&').replaceAll('<', '<').replaceAll('>', '>') +} + function buildTemplateText( locale: BotLocale, currency: 'GEL' | 'USD', categories: readonly string[] -): string { +): { + text: string + parseMode: 'HTML' +} { const t = getBotTranslations(locale).reminders - return [ - t.templateIntro(currency), - '', - ...categories.map((category) => `${category}: `), - '', - t.templateInstruction - ].join('\n') + const templateLines = categories.map((category) => `${category}: `).join('\n') + + return { + text: [ + escapeHtml(t.templateIntro(currency)), + '', + `
${escapeHtml(templateLines)}
`, + '', + escapeHtml(t.templateInstruction) + ].join('\n'), + parseMode: 'HTML' + } } function reminderUtilitySummaryText( @@ -261,7 +273,10 @@ function buildReminderConfirmationPayload(input: { async function replyInTopic( ctx: Context, text: string, - replyMarkup?: InlineKeyboardMarkup + replyMarkup?: InlineKeyboardMarkup, + options?: { + parseMode?: 'HTML' + } ): Promise { const message = ctx.msg if (!ctx.chat || !message) { @@ -286,6 +301,11 @@ async function replyInTopic( ? { reply_markup: replyMarkup as InlineKeyboardMarkup } + : {}), + ...(options?.parseMode + ? { + parse_mode: options.parseMode + } : {}) }) } @@ -481,14 +501,14 @@ export function registerReminderTopicUtilities(options: { await ctx.answerCallbackQuery({ text: t.templateToast }) - await replyInTopic( - ctx, - buildTemplateText( - reminderContext.locale, - reminderContext.currency, - reminderContext.categories - ) + const template = buildTemplateText( + reminderContext.locale, + reminderContext.currency, + reminderContext.categories ) + await replyInTopic(ctx, template.text, undefined, { + parseMode: template.parseMode + }) } options.bot.callbackQuery(REMINDER_UTILITY_GUIDED_CALLBACK, async (ctx) => {