Update bot features and localization
- Added new buttons and messages in Russian localization for improved user interaction. - Integrated '@grammyjs/menu' version 1.3.1 to enhance menu functionality. - Refactored bot command handlers to streamline conversation flows and improve code organization. - Updated the main menu structure to include new options for adding contacts and subscription information.
This commit is contained in:
parent
24aabae434
commit
ec3c2869c1
@ -46,6 +46,15 @@ commands-list =
|
||||
support =
|
||||
{ -support-contact }
|
||||
|
||||
# Кнопки
|
||||
btn-add-contact = 👤 Добавить контакт
|
||||
btn-share-bot = 🤝 Поделиться ботом
|
||||
btn-pro = 👑 Pro доступ
|
||||
btn-subscribe = 👑 Приобрести Pro
|
||||
btn-pro-info = ℹ️ Мой Pro доступ
|
||||
btn-open-app = 📱 Открыть приложение
|
||||
btn-back = ◀️ Назад
|
||||
|
||||
|
||||
# Приветственные сообщения
|
||||
msg-welcome =
|
||||
@ -91,6 +100,7 @@ err-banned = 🚫 Ваш аккаунт заблокирован
|
||||
err-with-details = ❌ Произошла ошибка
|
||||
{ $error }
|
||||
err-limit-exceeded = 🚫 Слишком много запросов! Подождите немного
|
||||
err-missing-telegram-id = ❌ Telegram ID не найден
|
||||
|
||||
|
||||
# Сообщения о доступе
|
||||
@ -99,12 +109,15 @@ msg-subscribe =
|
||||
• Разблокирует неограниченное количество заказов
|
||||
msg-subscribe-success = ✅ Платеж успешно обработан!
|
||||
msg-subscribe-error = ❌ Произошла ошибка при обработке платежа
|
||||
msg-subscription-inactive = 🔴 Pro доступ неактивен
|
||||
msg-subscription-active = 🟢 Ваш Pro доступ активен
|
||||
msg-subscription-active-until = 👑 Ваш Pro доступ активен до { $date }
|
||||
msg-subscription-active-days = 👑 Осталось дней вашего Pro доступа: { $days }
|
||||
msg-subscription-active-days-short = Осталось дней: { $days }
|
||||
msg-subscription-expired =
|
||||
Ваш Pro доступ истек.
|
||||
Воспользуйтесь командой /subscribe, чтобы получить неограниченное количество заказов
|
||||
msg-subscribe-disabled = 🚫 Pro доступ отключен для всех. Ограничения сняты! Наслаждайтесь полным доступом! 🎉
|
||||
msg-subscribe-disabled = 🟢 Pro доступ отключен для всех. Ограничения сняты! Наслаждайтесь полным доступом! 🎉
|
||||
|
||||
# Информация о лимитах
|
||||
msg-remaining-orders-this-month = 🧾 Доступно заказов в этом месяце: { $count }
|
||||
@ -17,6 +17,7 @@
|
||||
"@grammyjs/conversations": "^2.1.0",
|
||||
"@grammyjs/hydrate": "^1.6.0",
|
||||
"@grammyjs/i18n": "^1.1.2",
|
||||
"@grammyjs/menu": "^1.3.1",
|
||||
"@grammyjs/parse-mode": "^2.2.0",
|
||||
"@grammyjs/ratelimiter": "^1.2.1",
|
||||
"@grammyjs/runner": "^2.0.3",
|
||||
@ -29,7 +30,7 @@
|
||||
"grammy": "^1.38.1",
|
||||
"ioredis": "^5.7.0",
|
||||
"pino": "^9.9.0",
|
||||
"pino-pretty": "^13.1.1",
|
||||
"pino-pretty": "^13.1.1",
|
||||
"radashi": "catalog:",
|
||||
"tsup": "^8.5.0",
|
||||
"typescript": "catalog:",
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
/* eslint-disable id-length */
|
||||
import { type Context } from '@/bot/context';
|
||||
import { KEYBOARD_REMOVE, KEYBOARD_SHARE_BOT, KEYBOARD_SHARE_PHONE } from '@/config/keyboards';
|
||||
import { KEYBOARD_SHARE_BOT, KEYBOARD_SHARE_PHONE } from '@/config/keyboards';
|
||||
import { combine } from '@/utils/messages';
|
||||
import { isValidPhoneNumber, normalizePhoneNumber } from '@/utils/phone';
|
||||
import { type Conversation } from '@grammyjs/conversations';
|
||||
@ -74,9 +74,7 @@ export async function addContact(conversation: Conversation<Context, Context>, c
|
||||
await ctx.reply(
|
||||
await conversation.external(({ t }) => t('err-with-details', { error: String(error) })),
|
||||
);
|
||||
} finally {
|
||||
await ctx.reply(await conversation.external(({ t }) => t('commands-list')), KEYBOARD_REMOVE);
|
||||
}
|
||||
|
||||
return ctx.reply(await conversation.external(({ t }) => t('err-generic')), KEYBOARD_REMOVE);
|
||||
return conversation.halt();
|
||||
}
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { handleAddContact } from '../handlers/add-contact';
|
||||
import { type Context } from '@/bot/context';
|
||||
import { logHandle } from '@/bot/helpers/logging';
|
||||
import { Composer } from 'grammy';
|
||||
@ -6,8 +7,6 @@ const composer = new Composer<Context>();
|
||||
|
||||
const feature = composer.chatType('private');
|
||||
|
||||
feature.command('addcontact', logHandle('command-add-contact'), async (ctx) => {
|
||||
await ctx.conversation.enter('addContact');
|
||||
});
|
||||
feature.command('addcontact', logHandle('command-add-contact'), handleAddContact);
|
||||
|
||||
export { composer as addContact };
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
import { type Context } from '@/bot/context';
|
||||
import { logHandle } from '@/bot/helpers/logging';
|
||||
import { KEYBOARD_REMOVE } from '@/config/keyboards';
|
||||
import { combine } from '@/utils/messages';
|
||||
import { mainMenu } from '@/config/keyboards';
|
||||
import { Composer } from 'grammy';
|
||||
|
||||
const composer = new Composer<Context>();
|
||||
@ -9,10 +8,7 @@ const composer = new Composer<Context>();
|
||||
const feature = composer.chatType('private');
|
||||
|
||||
feature.command('help', logHandle('command-help'), async (ctx) => {
|
||||
return ctx.reply(combine(ctx.t('commands-list'), ctx.t('support')), {
|
||||
...KEYBOARD_REMOVE,
|
||||
parse_mode: 'HTML',
|
||||
});
|
||||
return ctx.reply(ctx.t('support'), { reply_markup: mainMenu });
|
||||
});
|
||||
|
||||
export { composer as help };
|
||||
|
||||
@ -1,39 +1,11 @@
|
||||
import { handlePro } from '../handlers/pro';
|
||||
import { type Context } from '@/bot/context';
|
||||
import { logHandle } from '@/bot/helpers/logging';
|
||||
import { combine } from '@/utils/messages';
|
||||
import { SubscriptionsService } from '@repo/graphql/api/subscriptions';
|
||||
import { Composer } from 'grammy';
|
||||
|
||||
const composer = new Composer<Context>();
|
||||
const feature = composer.chatType('private');
|
||||
|
||||
feature.command('pro', logHandle('command-pro'), async (ctx) => {
|
||||
const telegramId = ctx.from.id;
|
||||
const subscriptionsService = new SubscriptionsService({ telegramId });
|
||||
|
||||
const { subscriptionSetting } = await subscriptionsService.getSubscriptionSettings();
|
||||
const proEnabled = subscriptionSetting?.proEnabled;
|
||||
|
||||
if (!proEnabled) return ctx.reply(ctx.t('msg-subscribe-disabled'));
|
||||
|
||||
const { hasActiveSubscription, remainingDays, remainingOrdersCount } =
|
||||
await subscriptionsService.getSubscription({ telegramId });
|
||||
|
||||
if (hasActiveSubscription && remainingDays > 0) {
|
||||
return ctx.reply(
|
||||
combine(
|
||||
ctx.t('msg-subscription-active-days', { days: remainingDays }),
|
||||
remainingDays === 0 ? ctx.t('msg-subscription-expired') : '',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return ctx.reply(
|
||||
combine(
|
||||
ctx.t('msg-remaining-orders-this-month', { count: remainingOrdersCount }),
|
||||
remainingOrdersCount === 0 ? ctx.t('msg-subscription-expired') : '',
|
||||
),
|
||||
);
|
||||
});
|
||||
feature.command('pro', logHandle('command-pro'), handlePro);
|
||||
|
||||
export { composer as pro };
|
||||
|
||||
@ -1,15 +1,12 @@
|
||||
import { handleShareBot } from '../handlers/share-bot';
|
||||
import { type Context } from '@/bot/context';
|
||||
import { logHandle } from '@/bot/helpers/logging';
|
||||
import { KEYBOARD_SHARE_BOT } from '@/config/keyboards';
|
||||
import { Composer } from 'grammy';
|
||||
|
||||
const composer = new Composer<Context>();
|
||||
|
||||
const feature = composer.chatType('private');
|
||||
|
||||
feature.command('sharebot', logHandle('command-share-bot'), async (ctx) => {
|
||||
await ctx.reply(ctx.t('msg-contact-forward'), { parse_mode: 'HTML' });
|
||||
await ctx.reply(ctx.t('msg-share-bot'), { ...KEYBOARD_SHARE_BOT, parse_mode: 'HTML' });
|
||||
});
|
||||
feature.command('sharebot', logHandle('command-share-bot'), handleShareBot);
|
||||
|
||||
export { composer as shareBot };
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { handleSubscribe } from '../handlers/subscription';
|
||||
import { type Context } from '@/bot/context';
|
||||
import { logHandle } from '@/bot/helpers/logging';
|
||||
import { logger } from '@/utils/logger';
|
||||
@ -8,25 +9,13 @@ const composer = new Composer<Context>();
|
||||
|
||||
// Telegram требует отвечать на pre_checkout_query
|
||||
composer.on('pre_checkout_query', logHandle('pre-checkout-query'), async (ctx) => {
|
||||
console.log('🚀 ~ ctx:', ctx);
|
||||
await ctx.answerPreCheckoutQuery(true);
|
||||
});
|
||||
|
||||
const feature = composer.chatType('private');
|
||||
|
||||
// команда для входа в flow подписки
|
||||
feature.command('subscribe', logHandle('command-subscribe'), async (ctx) => {
|
||||
const telegramId = ctx.from.id;
|
||||
const subscriptionsService = new SubscriptionsService({ telegramId });
|
||||
|
||||
const { subscriptionSetting } = await subscriptionsService.getSubscriptionSettings();
|
||||
|
||||
const proEnabled = subscriptionSetting?.proEnabled;
|
||||
|
||||
if (!proEnabled) return ctx.reply(ctx.t('msg-subscribe-disabled'));
|
||||
|
||||
return ctx.conversation.enter('subscription');
|
||||
});
|
||||
feature.command('subscribe', logHandle('command-subscribe'), handleSubscribe);
|
||||
|
||||
// успешная оплата
|
||||
feature.on(':successful_payment', logHandle('successful-payment'), async (ctx) => {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { type Context } from '@/bot/context';
|
||||
import { logHandle } from '@/bot/helpers/logging';
|
||||
import { KEYBOARD_REMOVE, KEYBOARD_SHARE_PHONE } from '@/config/keyboards';
|
||||
import { KEYBOARD_SHARE_PHONE, mainMenu } from '@/config/keyboards';
|
||||
import { combine } from '@/utils/messages';
|
||||
import { CustomersService } from '@repo/graphql/api/customers';
|
||||
import { Composer } from 'grammy';
|
||||
@ -17,13 +17,9 @@ feature.command('start', logHandle('command-start'), async (ctx) => {
|
||||
|
||||
if (customer) {
|
||||
// Пользователь уже зарегистрирован — приветствуем
|
||||
return ctx.reply(
|
||||
combine(ctx.t('msg-welcome-back', { name: customer.name }), ctx.t('commands-list')),
|
||||
{
|
||||
...KEYBOARD_REMOVE,
|
||||
parse_mode: 'HTML',
|
||||
},
|
||||
);
|
||||
return ctx.reply(combine(ctx.t('msg-welcome-back', { name: customer.name })), {
|
||||
reply_markup: mainMenu,
|
||||
});
|
||||
}
|
||||
|
||||
// Новый пользователь — просим поделиться номером
|
||||
|
||||
7
apps/bot/src/bot/handlers/add-contact.ts
Normal file
7
apps/bot/src/bot/handlers/add-contact.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import { type Context } from '@/bot/context';
|
||||
|
||||
async function handler(ctx: Context) {
|
||||
await ctx.conversation.enter('addContact');
|
||||
}
|
||||
|
||||
export { handler as handleAddContact };
|
||||
4
apps/bot/src/bot/handlers/index.ts
Normal file
4
apps/bot/src/bot/handlers/index.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export * from './add-contact';
|
||||
export * from './pro';
|
||||
export * from './share-bot';
|
||||
export * from './subscription';
|
||||
41
apps/bot/src/bot/handlers/pro.ts
Normal file
41
apps/bot/src/bot/handlers/pro.ts
Normal file
@ -0,0 +1,41 @@
|
||||
import { type Context } from '@/bot/context';
|
||||
import { combine } from '@/utils/messages';
|
||||
import { SubscriptionsService } from '@repo/graphql/api/subscriptions';
|
||||
|
||||
async function handler(ctx: Context) {
|
||||
const telegramId = ctx?.from?.id;
|
||||
|
||||
if (!telegramId) throw new Error(ctx.t('err-missing-telegram-id'));
|
||||
|
||||
const subscriptionsService = new SubscriptionsService({ telegramId });
|
||||
|
||||
const { subscriptionSetting } = await subscriptionsService.getSubscriptionSettings();
|
||||
const proEnabled = subscriptionSetting?.proEnabled;
|
||||
|
||||
if (!proEnabled) {
|
||||
await ctx.reply(ctx.t('msg-subscribe-disabled'));
|
||||
}
|
||||
|
||||
const { hasActiveSubscription, remainingDays, remainingOrdersCount } =
|
||||
await subscriptionsService.getSubscription({ telegramId });
|
||||
|
||||
if (hasActiveSubscription && remainingDays > 0) {
|
||||
await ctx.reply(
|
||||
combine(
|
||||
ctx.t('msg-subscription-active'),
|
||||
ctx.t('msg-subscription-active-days-short', { days: remainingDays }),
|
||||
remainingDays === 0 ? ctx.t('msg-subscription-expired') : '',
|
||||
),
|
||||
);
|
||||
} else {
|
||||
await ctx.reply(
|
||||
combine(
|
||||
ctx.t('msg-subscription-inactive'),
|
||||
ctx.t('msg-remaining-orders-this-month', { count: remainingOrdersCount }),
|
||||
remainingOrdersCount === 0 ? ctx.t('msg-subscription-expired') : '',
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export { handler as handlePro };
|
||||
9
apps/bot/src/bot/handlers/share-bot.ts
Normal file
9
apps/bot/src/bot/handlers/share-bot.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import { type Context } from '@/bot/context';
|
||||
import { KEYBOARD_SHARE_BOT } from '@/config/keyboards';
|
||||
|
||||
async function handler(ctx: Context) {
|
||||
await ctx.reply(ctx.t('msg-contact-forward'), { parse_mode: 'HTML' });
|
||||
await ctx.reply(ctx.t('msg-share-bot'), { ...KEYBOARD_SHARE_BOT, parse_mode: 'HTML' });
|
||||
}
|
||||
|
||||
export { handler as handleShareBot };
|
||||
22
apps/bot/src/bot/handlers/subscription.ts
Normal file
22
apps/bot/src/bot/handlers/subscription.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import { type Context } from '@/bot/context';
|
||||
import { SubscriptionsService } from '@repo/graphql/api/subscriptions';
|
||||
|
||||
async function handler(ctx: Context) {
|
||||
const telegramId = ctx?.from?.id;
|
||||
|
||||
if (!telegramId) throw new Error(ctx.t('err-missing-telegram-id'));
|
||||
|
||||
const subscriptionsService = new SubscriptionsService({ telegramId });
|
||||
|
||||
const { subscriptionSetting } = await subscriptionsService.getSubscriptionSettings();
|
||||
|
||||
const proEnabled = subscriptionSetting?.proEnabled;
|
||||
|
||||
if (proEnabled) {
|
||||
await ctx.conversation.enter('subscription');
|
||||
} else {
|
||||
await ctx.reply(ctx.t('msg-subscribe-disabled'));
|
||||
}
|
||||
}
|
||||
|
||||
export { handler as handleSubscribe };
|
||||
@ -5,9 +5,9 @@ import { unhandledFeature } from './features/unhandled';
|
||||
import { errorHandler } from './handlers/errors';
|
||||
import { i18n } from './i18n';
|
||||
import * as middlewares from './middlewares';
|
||||
import { setCommands } from './settings/commands';
|
||||
import { setInfo } from './settings/info';
|
||||
import { setCommands, setInfo } from './settings';
|
||||
import { env } from '@/config/env';
|
||||
import { mainMenu } from '@/config/keyboards';
|
||||
import { getRedisInstance } from '@/utils/redis';
|
||||
import { autoChatAction, chatAction } from '@grammyjs/auto-chat-action';
|
||||
import { createConversation, conversations as grammyConversations } from '@grammyjs/conversations';
|
||||
@ -51,6 +51,8 @@ export function createBot({ token }: Parameters_) {
|
||||
bot.use(createConversation(conversation));
|
||||
}
|
||||
|
||||
bot.use(mainMenu);
|
||||
|
||||
setInfo(bot);
|
||||
setCommands(bot);
|
||||
|
||||
|
||||
@ -1,3 +1,7 @@
|
||||
import { env } from './env';
|
||||
import { type Context } from '@/bot/context';
|
||||
import { handleAddContact, handlePro, handleShareBot, handleSubscribe } from '@/bot/handlers';
|
||||
import { Menu } from '@grammyjs/menu';
|
||||
import {
|
||||
type InlineKeyboardMarkup,
|
||||
type ReplyKeyboardMarkup,
|
||||
@ -30,9 +34,27 @@ export const KEYBOARD_SHARE_BOT = {
|
||||
[
|
||||
{
|
||||
text: ' Воспользоваться ботом',
|
||||
url: process.env.BOT_URL as string,
|
||||
url: env.BOT_URL + '?start=new',
|
||||
},
|
||||
],
|
||||
],
|
||||
} as InlineKeyboardMarkup,
|
||||
};
|
||||
|
||||
// Главное меню
|
||||
export const mainMenu = new Menu<Context>('main-menu', { autoAnswer: true })
|
||||
.text((ctx) => ctx.t('btn-add-contact'), handleAddContact)
|
||||
.row()
|
||||
.text((ctx) => ctx.t('btn-subscribe'), handleSubscribe)
|
||||
.text((ctx) => ctx.t('btn-pro-info'), handlePro)
|
||||
.row()
|
||||
.text((ctx) => ctx.t('btn-share-bot'), handleShareBot)
|
||||
.row()
|
||||
.url(
|
||||
(ctx) => ctx.t('btn-open-app'),
|
||||
() => {
|
||||
const botUrl = new URL(env.BOT_URL);
|
||||
botUrl.searchParams.set('startapp', '');
|
||||
return botUrl.toString();
|
||||
},
|
||||
);
|
||||
|
||||
13
pnpm-lock.yaml
generated
13
pnpm-lock.yaml
generated
@ -117,6 +117,9 @@ importers:
|
||||
'@grammyjs/i18n':
|
||||
specifier: ^1.1.2
|
||||
version: 1.1.2(grammy@1.38.1)
|
||||
'@grammyjs/menu':
|
||||
specifier: ^1.3.1
|
||||
version: 1.3.1(grammy@1.38.1)
|
||||
'@grammyjs/parse-mode':
|
||||
specifier: ^2.2.0
|
||||
version: 2.2.0(grammy@1.38.1)
|
||||
@ -1374,6 +1377,12 @@ packages:
|
||||
peerDependencies:
|
||||
grammy: ^1.10.0
|
||||
|
||||
'@grammyjs/menu@1.3.1':
|
||||
resolution: {integrity: sha512-HJslY/n76T1Ar5qDDhNtjLs+PpcrlB9aGsXu3CJHLt147DC3K3lpiRvRW/Xh9/x9hqYVw7KKbnvsQXVgzoU81Q==}
|
||||
engines: {node: '>=12.20.0 || >=14.13.1'}
|
||||
peerDependencies:
|
||||
grammy: ^1.31.0
|
||||
|
||||
'@grammyjs/parse-mode@2.2.0':
|
||||
resolution: {integrity: sha512-sI5xjXYn1ihEEf1bJx4ew2KPsX1O3jsd2V/MpA1CX2tCYlxquidr7agk4IOR5bGEK38pyNVxVBdyCiy/eMxEfQ==}
|
||||
engines: {node: '>=14.13.1'}
|
||||
@ -7771,6 +7780,10 @@ snapshots:
|
||||
'@fluent/langneg': 0.6.2
|
||||
grammy: 1.38.1
|
||||
|
||||
'@grammyjs/menu@1.3.1(grammy@1.38.1)':
|
||||
dependencies:
|
||||
grammy: 1.38.1
|
||||
|
||||
'@grammyjs/parse-mode@2.2.0(grammy@1.38.1)':
|
||||
dependencies:
|
||||
grammy: 1.38.1
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user